这是一个艰难的。 TypeScript缺少mapped conditional types和一般recursive type definitions,这两个都是我想用来给你的联合类型。有你想要的一些症结点:
- 一个
RouteEntry
的nested
性能有时会null
,并键入评估为keyof null
或null[keyof null]
开始打破东西的表达。一个需要小心。我的解决方法包括添加一个虚拟键,使其不会为空,然后在最后删除它。
- 无论您使用什么类型的别名(称为
RouteListNestedKeys<X>
)似乎都需要根据自身定义,并且您将收到“循环引用”错误。一种解决方法是提供一些适用于某些有限嵌套级别的东西(例如,深度为9级)。这可能会导致编译器放慢速度,因为它可能会急切地评估所有9个级别,而不是延迟评估,直到稍后。
- 这需要大量的涉及映射类型的类型别名组合,并且有一个bug组合映射类型,直到TypeScript 2.6才会被修复。 A workaround涉及使用通用默认类型参数。
- “在最后删除虚拟键”步骤涉及一个称为
Diff
的类型操作,它至少需要TypeScript 2.4才能正常运行。
这意味着:我有一个解决方案,但我警告你,它很复杂和疯狂。最后一件事之前,我把代码:您需要更改
export const list: RouteList = { // ...
到
export const list = { // ...
也就是说,从list
变量删除类型标注。如果您将其指定为RouteList
,那么您会丢弃TypeScript关于list
确切结构的知识,并且您将只获得string
作为关键类型。通过忽略注释,你让TypeScript推断这个类型,因此它会记住整个嵌套结构。
好了,这里有云:
type EmptyRouteList = {[K in 'remove_this_value']: RouteEntry};
type ValueOf<T> = T[keyof T];
type Diff<T extends string, U extends string> = ({[K in T]: K} &
{[K in U]: never} & { [K: string]: never })[T];
type N0<X extends RouteList> = keyof X
type N1<X extends RouteList, Y = {[K in keyof X]: N0<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N2<X extends RouteList, Y = {[K in keyof X]: N1<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N3<X extends RouteList, Y = {[K in keyof X]: N2<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N4<X extends RouteList, Y = {[K in keyof X]: N3<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N5<X extends RouteList, Y = {[K in keyof X]: N4<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N6<X extends RouteList, Y = {[K in keyof X]: N5<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N7<X extends RouteList, Y = {[K in keyof X]: N6<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N8<X extends RouteList, Y = {[K in keyof X]: N7<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type N9<X extends RouteList, Y = {[K in keyof X]: N8<X[K]['nested'] & EmptyRouteList>}> = keyof X | ValueOf<Y>
type RouteListNestedKeys<X extends RouteList, Y = Diff<N9<X>,'remove_this_value'>> = Y;
让我们尝试一下:
export const list = {
'/parent': {
name: 'parentTitle',
nested: {
'/child': {
name: 'child',
nested: null,
},
},
},
'/another': {
name: 'anotherTitle',
nested: null
},
}
type ListNestedKeys = RouteListNestedKeys<typeof list>
如果检查ListNestedKeys
,你会看到它是"parent" | "another" | "child"
,因为你想要的。这取决于你是否值得。
Whe!希望有所帮助。祝你好运!
一个小细节:如果您从RouteList中删除索引签名'readonly [key:string]:RouteEntry',这一切仍然有效。显然,当你提供实际泛型参数为'typeof list'时,索引签名从不用于类型推断。这使您可以在每个地方写入'extends {}'而不是'extends RouteList',并且完全从答案中移除'RouteList'接口。 – artem