这是一个艰难的过程。 TypeScript 缺少 mapped conditional types 和 通用的recursive type definitions,这两个都是我想用来为您提供联合类型的东西。 (编辑 2019-04-05:conditional types 在 TS2.8 中引入)您想要的有一些症结所在:
-
RouteEntry 的nested 属性有时可以是null,并且键入计算结果为keyof null 或null[keyof null] 的表达式开始破坏事物。一个需要小心。我的解决方法是添加一个虚拟密钥,使其永远不会为空,然后在最后将其删除。
- 无论你使用什么类型的别名(称之为
RouteListNestedKeys<X>)似乎都需要根据自身来定义,你会得到一个“循环引用”的错误。一种解决方法是提供可以工作到某个有限嵌套级别(例如,9 层深)的东西。这可能会导致编译器变慢,因为它可以急切地评估所有 9 个级别,而不是将评估推迟到以后。
- 这需要大量涉及映射类型的类型别名组合,并且有一个 bug 组合映射类型,直到 TypeScript 2.6 才会修复。 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",正如您所愿。值不值,看你自己了。
哇!希望有帮助。祝你好运!