typescript - 递归获取对象/记录的属性名称

标签 typescript

给定形式为 {...} 的对象 - 不是原始类型或数组 - 我想生成一个类型,该类型是该类型中的文字属性名称。我试过用映射类型来做这个,但不能正确。不知道我做错了什么。我可以深入一层(这实际上很容易使用 keyof 运算符),但无法弄清楚如何递归地进行。我知道我很接近,因为在下面的示例中,如果 email属性不是可选的,它可以工作。所以不知何故,它被可选属性绊倒了。

type PropertyNames<
  T extends Record<string, unknown>,
  MODE extends 'deep' | 'shallow'
> = {
  [KEY in keyof T & (string | number )]:
    | KEY
    | (MODE extends 'deep'
        ? T[KEY] extends Record<string, unknown>
          ? PropertyNames<T[KEY], 'deep'>
          : never
        : never);
}[keyof T & (string | number)];

type Email = {
  address: string;
  verified: boolean;
};

type UserToken = {
  uid: string;
  name?: string;
  email?: Email;
};

// Expected: uid | name | email | address | verified
// Actual  : uid | name | email
type DeepNames = PropertyNames<UserToken, 'deep'>;

// Expected and Actual : uid | name | email
type ShallowNames = PropertyNames<UserToken, 'shallow'>;

最佳答案

我认为您的主要问题是 T[KEY] extends Record<string, unknown> ? ...不是 distributiveT[KEY] 中可能的联合,等等当 KEY是可选属性,T[KEY]将包括 undefined ,以及自 undefined extends Record<string, unknown>不是真的,整个事情都失败了,你会得到 never .最好使用分布在工会上的公式。
我的倾向是做这样的事情:

type PropertyNames<T, M extends 'deep' | 'shallow'> =
    T extends object ?
    { [K in keyof T]-?: K |
        (M extends 'deep' ? PropertyNames<T[K], "deep"> : never)
    }[keyof T] : never
或者,如果您在其中混合了数组类型,则:
type PropertyNames<T, M extends 'deep' | 'shallow'> =
    T extends object ?
    { [K in keyof T]-?: K |
        (M extends 'deep' ? PropertyNames<T[K], "deep"> : never)
    }[Extract<keyof T, T extends readonly any[] ? number : unknown>] : never
这为您的示例产生了这一点:
type DeepNames = PropertyNames<UserToken, 'deep'>;
// type DeepNames = "uid" | "name" | "email" | "address" | "verified"
如预期的。

我所做的改变:
  • 我已经放弃了 Record<string, unknown>对于问题较少的 object , 自 interface没有索引签名的类型往往不能分配给 Record<string, unknown> :
    interface Oops {
      x: string;
    }
    
    type Nope = Oops extends Record<string, unknown> ? "yep" : "nope";
    // type Nope = "nope"
    
  • 我已经取消了对 T 的限制是一个对象类型;这使得在 PropertyNames<T[K]> 上进行递归评估变得更容易。 ;如果 T不是对象则返回 never .这也会自动将操作分配给 T 中的联合。 .虽然 T[K] extends ...不分发,T extends ...是因为 T是一个“裸”类型参数。
  • 我已经使用 the -? modifier 使映射类型将所有属性转换为所需的属性。 ;这将消除任何奇怪的undefined s 可能出现在最终输出中。
  • 我已更换 keyof T & (string | number)keyof T因为除非我们试图禁止 symbol键我认为这并不重要。
  • Extract<keyof T, T extends readonly any[] ? number : unknown>的事只是处理数组;你大概不想看到数组方法的所有名称,比如 "push""pop"在你的输出中(如果你这样做,你可以使用 keyof T 代替)。因此,对于数组,只需查看数字索引处的属性。

  • Playground link to code

    关于typescript - 递归获取对象/记录的属性名称,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66379051/

    相关文章:

    angular - ng build 和 ngc 有什么关系和区别

    typescript - 错误 : Cannot find module '@vue/cli-plugin-babel/preset'

    json - 当 HttpClient 映射不会时,如何正确解析 TypeScript 类的 Date 对象?

    javascript - 扩展 Material UI ListItem 的正确方法是什么?

    c# - 错误: "the application completed without reading the entire request body" Angular/C#

    angular - 形式中的形式 + angular5

    typescript - 用 Jest 测试 typescript 中的私有(private)函数

    javascript - TypeScript:有没有办法扩展多个接口(interface)并合并相同的属性?

    generics - TypeScript 报告泛型函数中不存在的属性

    javascript - Angular 2 中的表格