我有一个递归类型的对象,我想获取某个类型的键和任何子键。
例如。下面我想得到一个联合类型:
'/another' | '/parent' | '/child'
示例:
export interface RouteEntry {
readonly name: string,
readonly nested : RouteList | null
}
export interface RouteList {
readonly [key : string] : RouteEntry
}
export const list : RouteList = {
'/parent': {
name: 'parentTitle',
nested: {
'/child': {
name: 'child',
nested: null,
},
},
},
'/another': {
name: 'anotherTitle',
nested: null
},
}
在打字稿中,您可以使用keyof typeof RouteList来获取联合类型:
'/another' | '/parent'
是否还有一种方法可以包含嵌套类型
答案 0 :(得分:6)
这是一个艰难的。 TypeScript缺少 mapped conditional types和 general recursive type definitions,这两者都是我想要用来为你提供联合类型的东西。 (编辑2019-04-05:conditional types在TS2.8中被引入)你想要的东西有一些难点:
nested
的{{1}}属性有时可能是RouteEntry
,而评估为null
或keyof null
的类型表达式会开始破坏。一个人需要小心。我的解决方法是添加一个虚拟密钥,使其永远不为空,然后在最后删除它。null[keyof null]
)似乎都需要根据自身定义,并且您将收到“循环引用”错误。一种解决方法是提供一些可以达到某种有限嵌套水平的东西(例如,9级深度)。这可能会导致编译器减慢速度,因为它可能急切地评估所有9个级别,而不是将评估推迟到以后。Diff
的类型操作,该操作至少需要TypeScript 2.4才能正常运行。这意味着:我有一个有效的解决方案,但我警告你,这是复杂而疯狂的。在我放入代码之前的最后一件事:你需要改变
RouteListNestedKeys<X>
到
export const list: RouteList = { // ...
即,从export const list = { // ...
变量中删除类型注释。如果您将其指定为list
,则会丢弃TypeScript对RouteList
的确切结构的了解,除了list
之外,您将只获得任何密钥类型。通过省略注释,您可以让TypeScript推断出类型,因此它将记住整个嵌套结构。
好的,这里有:
string
让我们试一试:
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
,如您所愿。这取决于你是否值得。
呼!希望有所帮助。祝你好运!
答案 1 :(得分:2)
这是一个无限递归的解决方案:
type Paths<T> = T extends RouteList
? keyof T | { [K in keyof T]: Paths<T[K]['nested']> }[keyof T]
: never
type ListPaths = Paths<typeof list> // -> "/parent" | "/another" | "/child"
在Typescript v3.5.1上测试。另外,您还需要按照@jcalz的建议,从list
变量中删除类型注释。
答案 2 :(得分:0)
@Aleksi 的回答展示了如何获取请求的类型联合 "/parent" | "/another" | "/child"
。
由于问题涉及路线层次结构,我想补充一点,从 typescript 4.1 开始,功能 Template Literal Types 不仅可用于获取所有密钥,还可以用于生成所有可能的路线 :"/parent" | "/another" | "/parent/child"
type ValueOf<T> = T[keyof T]
type AllPaths<T extends RouteList, Path extends string = ''> = ValueOf<{
[K in keyof T & string]:
T[K]['nested'] extends null
? K
: (`${Path}${K}` | `${Path}${K}${AllPaths<Extract<T[K]['nested'], RouteList>>}`)
}>
type AllRoutes = AllPaths<typeof list> // -> "/parent" | "/another" | "/parent/child"
与其他答案一样,list
对象可能没有 RouteList
类型注释,因为它会删除我们的 AllPaths
类型操作的类型信息。