嵌套子对象的键 [英] Keyof nested child objects

查看:23
本文介绍了嵌套子对象的键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a recursively typed object that I want to get the keys of and any child keys of a certain type.

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

For instance. Below I want to get a union type of:

'/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 来获取联合类型:

In typescript you can use keyof typeof RouteList to get the union type:

'/another' | '/parent' 

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

Is there a method to also include the nested types

推荐答案

这很困难.TypeScript 缺少映射条件类型一般递归类型定义,我想用它们来为您提供联合类型.(编辑 2019-04-05:conditional类型 是在 TS2.8 中引入的)你想要的有一些症结:

That's a tough one. TypeScript lacks both mapped conditional types and general recursive type definitions, which are both what I'd want to use to give you that union type. (Edit 2019-04-05: conditional types were introduced in TS2.8) There are some sticking points with what you want:

  • RouteEntrynested 属性有时可以是 null,并且类型表达式的计算结果为 keyof nullnull[keyof null] 开始破坏事物.一个需要小心.我的解决方法是添加一个虚拟键,使其永远不会为空,然后在最后将其删除.
  • 无论您使用什么类型的别名(称之为 RouteListNestedKeys),似乎都需要根据自身进行定义,并且您将收到循环引用"错误.一种解决方法是提供可以达到某个有限嵌套级别(例如 9 级深)的内容.这可能会导致编译器变慢,因为它可以急切地评估所有 9 个级别,而不是推迟评估.
  • 这需要大量涉及映射类型的类型别名组合,并且存在一个错误 组合映射类型,直到 TypeScript 2.6 才会修复.解决方法涉及使用通用默认类型参数.
  • 最后删除一个虚拟键"步骤涉及一个名为的类型操作Diff 至少需要 TypeScript 2.4 才能正常运行.
  • The nested property of a RouteEntry can sometimes be null, and type expressions that evaluate to keyof null or null[keyof null] start to break things. One needs to be careful. My workaround involves adding a dummy key so that it's never null, and then removing it at the end.
  • Whatever type alias you use (call it RouteListNestedKeys<X>) seems to need to be defined in terms of itself, and you will get a "circular reference" error. A workaround would be to provide something that works up to some finite level of nesting (say, 9 levels deep). This might cause the compiler to slow way down, since it could eagerly evaluate all 9 levels instead of deferring the evaluation until later.
  • This needs a lot of type alias composition involving mapped types, and there is a bug with composing mapped types that won't be fixed until TypeScript 2.6. A workaround involves using generic default type parameters.
  • The "remove a dummy key at the end" step involves a type operation called Diff which needs at least TypeScript 2.4 to run properly.

这意味着:我有一个可行的解决方案,但我警告您,它既复杂又疯狂.在我插入代码之前的最后一件事:你需要改变

All that means: I have a solution which works, but I warn you, it's complex and crazy. One last thing before I drop in the code: you need to change

export const list: RouteList = { // ...

export const list = { // ...

即,从 list 变量中删除类型注释.如果你把它指定为 RouteList,你就丢掉了 TypeScript 对 list 的确切结构的了解,你只会得到 string 作为键类型.通过省略注释,您可以让 TypeScript 推断类型,因此它会记住整个嵌套结构.

That is, remove the type annotation from the list variable. If you specify it as RouteList, you are throwing away TypeScript's knowledge of the exact structure of list, and you will get nothing but string as the key type. By leaving off the annotation, you let TypeScript infer the type, and therefore it will remember the entire nested structure.

好的,开始:

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" |另一个" |孩子",如您所愿.这取决于你是否值得.

If you inspect ListNestedKeys you will see that it is "parent" | "another" | "child", as you wanted. It's up to you whether that was worth it or not.

哇!希望有帮助.祝你好运!

Whew! Hope that helps. Good luck!

这篇关于嵌套子对象的键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆