【问题标题】:In Typescript, how can retrieve a deeply nested property type where I know the key, but not the path?在 Typescript 中,如何在我知道键但不知道路径的情况下检索深度嵌套的属性类型?
【发布时间】:2021-11-09 18:03:51
【问题描述】:

我正在尝试创建一个泛型类型,该类型可以从潜在的嵌套对象类型中检索特定键的类型。它将接收对象和作为泛型的键。但是,该键的路径是未知的,因此它可能需要遍历每个值才能找到它。到目前为止,这是我想出的。

type GetDeepProp<T extends object, Key extends string> = Key extends keyof T 
  ? T[Key]
  : {
   [k in keyof T]: T[k] extends object 
    ? GetDeepProp<T[k], Key>
    : unknown
  }[keyof T]

//examples
type ShallowUser = { region: string; }
type DeepUser = { account: { region: number; } }
type DeeperUser = { account: { location: { region: "USA" | "Canada" | "France", } } }

let shallowRegion: GetDeepProp<ShallowUser, 'region'> // should return string
let deepRegion: GetDeepProp<DeepUser, 'region'> //should return number
let deeperRegion: GetDeepProp<DeeperUser, 'region'> //should return "USA" | "Canada"

在研究了一段时间后,我发现这个解决方案似乎确实有效。但我不明白为什么它会起作用。这最后一行[keyof T] create 不应该从所有键中创建所有类型的联合,而不仅仅是传入的键吗?我想我的问题是,为什么这行得通(或者它可能不成立),有没有更好的方法来完成这个?

【问题讨论】:

  • 我不知道是否有其他方法可以实现这一点,但您应该使用never 而不是unknown。因为这意味着您在某处找到了 region 属性,但您无法确定类型。但是never 类型意味着不存在的属性不能有类型。

标签: typescript typescript-typings typescript-generics


【解决方案1】:

最终由您决定在各种极端情况下您想看到什么,例如在对象的不同深度具有相同属性名称的多个属性,或者面对unions 或@987654322 时该怎么做@ 或 index signatures 等。现在我将主要忽略这些问题,因为几乎不可能预测所有可能的用途并解决它们。


以下是输入GetDeepProp&lt;T, K&gt;的方法:

type GetDeepProp<T extends object, K extends string> = K extends keyof T
  ? T[K] : { [P in keyof T]: GetDeepProp<Extract<T[P], object>, K> }[keyof T]

这与您的类似,除了 GetDeepProp&lt;Extract&lt;T[P], object&gt;, K&gt; 将在 T[P] 中使用 distribute over unions,并且在 T[P] 不是对象的情况下,这将计算为 never 而不是 unknown

确实{[P in keyof T]: ...}[keyof T] 是一个mapped type,您可以立即将index 放入其中以获得所有属性值的联合。这对您有用的原因是,对于不相关的键,您希望映射类型的属性值为the never type,因为对于所有类型XXX,编译器会将XXX | never 折叠为XXXnever 被吸收工会。

所以你不想要unknown,因为它与联合有相反的行为:对于所有类型XXX,编译器会将XXX | unknown 折叠成unknown。您不希望同级属性使用 unknown 消除您的结果:

type DeepUser = { account: { region: number; }, other: number }
let oops: OrigGetDeepProp<DeepUser, 'region'> // unknown for your definition
let deepRegion: GetDeepProp<DeepUser, 'region'> // number for new definition

other 属性使用您的GetDeepProp 版本破坏结果,而此版本提供number


给你。正如我所说,您可能会遇到一些用例,其中此定义会执行您不期望或不喜欢的事情。如果它们是次要的,我也许可以更新答案来处理它们。否则,您可能需要自己解决这个问题,或者发布一个具有更详细要求的新帖子。

但希望这至少解释了这里的一般方法,足以对您有用。

Playground link to code

【讨论】:

  • never 的联合和unknown 的联合的解释非常有帮助。我没有意识到这是这种情况。谢谢!
【解决方案2】:

最后一行 [keyof T] create 不应该从所有键中创建所有类型的联合,而不仅仅是传入的键吗?为什么这行得通(或者它可能不成立),有没有更好的方法来实现这一点?

不,它不应该,因为您的递归检查会进入对象的最深层,如果找不到匹配项,则不会创建类型映射。当您的对象在其他地方有另一个 region 属性时,这将是真的,如下所示:

type DeeperUser2 = { account: { location: { region: "USA" | "Canada" | "France", } },someOtherProp :{region :number} }
let deeperRegion2: GetDeepProp<DeeperUser2, 'region'> //  "USA" | "Canada" | "France" | number

Playground Link

【讨论】:

    猜你喜欢
    • 2023-03-17
    • 2019-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多