Keyof嵌套子对象

Ada*_*gan 9 typescript

我有一个递归类型的对象,我想获取某些类型的键和任何子键.

例如.下面我想得到一个联合类型:

'/another' | '/parent' | '/child'
Run Code Online (Sandbox Code Playgroud)

例:

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
    },
}
Run Code Online (Sandbox Code Playgroud)

在打字稿中,您可以使用keyof typeof RouteList来获取联合类型:

'/another' | '/parent' 
Run Code Online (Sandbox Code Playgroud)

是否有一种方法也包括嵌套类型

jca*_*alz 9

这是一个艰难的过程。TypeScript 没有映射的条件类型,也没有 通用的递归类型定义,这两种都是我想用来为您提供的联合类型。(编辑2019-04-05:条件类型在TS2.8中引入)有一些需要注意的问题:

  • a的nested属性RouteEntry有时可以是null,并且类型表达式可以求值keyof nullnull[keyof null]开始破坏事物。需要注意一点。我的解决方法是添加一个虚拟密钥,以使其永远不会为空,然后最后将其删除。
  • 无论您使用什么类型别名(称为RouteListNestedKeys<X>),似乎都需要对其本身进行定义,并且会出现“循环引用”错误。一种解决方法是提供一些可以在有限的嵌套级别(例如9层深)中工作的对象。这可能会导致编译器变慢,因为它可能会急切地评估所有9个级别,而不是将评估推迟到以后。
  • 这需要大量涉及映射类型的类型别名组合,并且存在一个组合映射类型的错误,直到TypeScript 2.6才会修复。一个解决方法包括使用通用的默认类型参数。
  • “最后删除伪密钥”步骤涉及一个称为类型的操作Diff,该操作至少需要TypeScript 2.4才能正常运行。

这意味着:我有一个可行的解决方案,但我警告您,它是复杂而疯狂的。在放入代码之前的最后一件事:您需要进行更改

export const list: RouteList = { // ...
Run Code Online (Sandbox Code Playgroud)

export const list = { // ...
Run Code Online (Sandbox Code Playgroud)

也就是说,从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;
Run Code Online (Sandbox Code Playgroud)

让我们尝试一下:

export const list = {
    '/parent': {
        name: 'parentTitle',
        nested: {
            '/child': {
                name: 'child',
                nested: null,
            },
        },
    },
    '/another': {
        name: 'anotherTitle',
        nested: null
    },
}

type ListNestedKeys = RouteListNestedKeys<typeof list> 
Run Code Online (Sandbox Code Playgroud)

如果您进行检查,ListNestedKeys您会发现它是"parent" | "another" | "child"所需的。是否值得这样做取决于您。

ew!希望能有所帮助。祝好运!


Ale*_*ksi 5

这是一个无限递归的解决方案:

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"
Run Code Online (Sandbox Code Playgroud)

在Typescript v3.5.1上测试。您还需要按照list@jcalz的建议从变量中删除类型注释。

  • 我喜欢这个答案(可能最近给出了一些版本),但我希望我知道这样的事情得到了官方的支持。递归地将属性向下推入类型的一个级别是安全的,因为此类类型是懒惰地求值的,但是递归地索引到对象中(将属性向上推到一个级别)可能会成为问题,因为此类类型的求值率很高。 (4认同)
  • @DanielSteigerwald:您能否指出打字稿问题中提到的地方,甚至只是给出一些关于问题可能是什么的提示? (4认同)