对于该结构的大部分,您正在寻找union https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types字符串的文字类型 https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html对应于某个固定深度的键。所以我们可以写一个实用程序类型KeysAtDepth<T, N>
所以说,KeysAtDepth<T, 0>
对应的键T
直接,并且KeysAtDepth<T, 1>
对应于所有属性的键T
, and KeysAtDepth<T, 2>
对应所有子属性的键T
, etc.
这是一种写法:
type KeysAtDepth<T, N extends number, A extends any[] = []> =
T extends unknown ? (
N extends A['length'] ? keyof T : KeysAtDepth<T[keyof T], N, [0, ...A]>
) : never;
这是一个递归条件类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#recursive-conditional-types。 TypeScript 无法真正进行数学运算number
直接输入(参见微软/TypeScript#26382 https://github.com/microsoft/TypeScript/issues/26382), 但它can表示追加到的操作元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types (via 可变元组类型 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#variadic-tuple-types)并检查他们的length
属性,所以我们可以编码“递归直到深度N
" as "在元组前面添加一个元素A
每次我们递归然后停止时A['length']
is N
".
递归的基本情况是返回keyof T
;否则我们申请KeysAtDepth
to T[keyof T]
,意味着所有属性的并集T
.
这解释了那里发生的大部分事情;剩下的唯一部分是解释为什么我们有T extends unknown ? ⋯ : never
,看起来它没有做任何事情。原因是为了让KeysAtDepth<T, N>
a 分配条件类型 https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types in T
, 以便KeysAtDepth<A | B, N>
相当于KeysAtDepth<A, N> | KeysAtDepth<B, N>
。如果没有它,你最终会得到类似的东西keyof (A | B)
这不是你想要的(它只会显示存在于all工会成员)。
让我们测试一下:
const greatgrandkids = {
Beth: {
Karen: {
Ava: ['Alice', 'Amelia'],
Emma: ['Sarah'],
},
Mary: {
Sophia: ['Grace'],
},
},
Ruth: {
Ella: {
Scarlett: ['Charlotte'],
},
},
} as const;
type GreatGrandKids = typeof greatgrandkids;
type GreatGrandMother = KeysAtDepth<GreatGrandKids, 0>
// type GreatGrandMother = "Beth" | "Ruth"
type GrandMother = KeysAtDepth<GreatGrandKids, 1>
// type GrandMother = "Karen" | "Mary" | "Ella"
type Mother = KeysAtDepth<GreatGrandKids, 2>;
// type Mother = "Ava" | "Emma" | "Sophia" | "Scarlett"
到目前为止,一切都很好。
理想情况下你的greatgrandkids
结构将是统一的,并且自始至终都使用按键,例如
const greatgrandkids = {
Beth: {
Karen: {
Ava: { Alice: {}, Amelia: {} },
Emma: { Sarah: {} }
},
Mary: {
Sophia: { Grace: {} }
},
},
Ruth: {
Ella: {
Scarlett: { Charlotte: {} }
},
},
} as const;
然后最终的类型就是深度的键3
:
type Child = KeysAtDepth<GreatGrandKids, 3>
// type Child = "Alice" | "Amelia" | "Sarah" | "Grace" | "Charlotte"
但事实并非如此,所以我们需要另一种实用程序类型,例如TypesAtDepth<T, N>
抓住联盟types在特定的深度。所以TypesAtDepth<T, 0>
将会T
, and TypesAtDepth<T, 1>
将会T[keyof T]
(深度 1 处类型的并集)等
几乎是一样的:唯一的区别是基本情况是T
代替keyof T
:
type TypesAtDepth<T, N extends number, A extends any[] = []> =
T extends unknown ? (
N extends A['length'] ? T : TypesAtDepth<T[keyof T], N, [0, ...A]>
) : never;
And now TypesAtDepth<GreatGrandKids, 3>
是一个联盟只读元组类型 https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-tuple-types包含字符串文字,我们可以index https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html with number
获取元素类型的并集:
type Child = TypesAtDepth<GreatGrandKids, 3>[number]
// type Child = "Alice" | "Amelia" | "Sarah" | "Grace" | "Charlotte"
Playground 代码链接 https://www.typescriptlang.org/play?#code/LAKFBcE8AcFMAIDStIGcCC4AitrgBYA8AKgDTwBy8sAHuLAHYAmq8DArgLYBGsATuXTU6jFvACGDSAG0AuvAC88OQD5FoePGLD6zVuwYBrBgHsA7g3gB+eAAoNmyjtGt00gOQAbRgHMC7+RtDFBMAMy14AC4kFAxsXAISaWDIMK1ZcgpyaQAGcgA6QvRZFQcASii2WAA3fgBuUEaQAGMTBlRweB8+WHFwbskmQwBLMSUAbwcAIVgCaMmQRyRxHoZ5hyX0avFoj3RPYebYd3J3dE5YA-EA0g3HAFFOTh3ldwBlFfF8G7uAX1vFo4ALIrSDrQFLN4maD4YYvDwAcT44iOPwh8H+DkxgIASuw5vAFkt7p5PC8iUt4G9mitvOBwLt3ABhfC0kz044ZP4AzTY34SVitdrgBogKBweBI3rgJGDRCjVhKcWwNLdaUDZgjFii5WSnp9WXMIHs-D8RQxNCYHB4IhSg3I5jyljkHKlEAAend8F1dplDqYxoIZqUACIZgQQ-AAD7wEN4iMQGAIQ0Bk3Bi1xa2JX0pp2ocgARjdnu9Sb1g0Dpr45pDiBWjEjMZDIL4kEbsZJZJDiYllfTyEt8RthBz-rz5AATCpRSXdX3q6GtuJ2yHHs8V1CYXCNzS+HTwN2wGKy8Qk5mEkQyE5aLoxBwePxBM49BIpHJzapzdoby54AZjOYlhWA4VA-i+bheL4-iBBE0SnnA57DsQyQhOExAZJQ2R5PAhT5MUajRAwNT1E0uossMnhMF+Z5WheI76n6coKuQADMKjSPevB8LIoCzmW5GUTW+yHLAK7nJc27RrGHzIvgK6ykcK4smyHKHqADDiBcqDQCiCAAKoMMMoQmHwnBvOAfDsM04DsD0hIOEKHRdAxGpDAq5oUpo4b4OClLLKsvl+fAS6BUFwUHEc8zYmFmjiVcUV3Es0VBWu5KJZSMlfAl6JJelyVLC2YL2TljibrCaUlUsCmwNlYW-HlPKOPl8Dxj5xV+Z2FVhdStKzAy7Uxcpe7svQtVBfVJXNcl-LiIKbQdKKDi6gOiGJFeoEiC+nGPsFz5iJIMjyEon5KN+m1iP+pgWNYdwbbergeN4DB+N8MEpGk2jRCttFIShqRoRhWTKNhuH4ZURG1Hwi2Aj6DG5u5SpJqqLn+lqqCipoZGwoJSjfUO2Zw2OzHwGxDh8RKAlUYuEWiVJIZxZJTaZXJdPVUprLDapTS-EAA