给定形式为 {...} 的对象 - 不是原始类型或数组 - 我想生成一个类型,该类型是该类型中的文字属性名称。我试过用映射类型来做这个,但不能正确。不知道我做错了什么。我可以深入一层(这实际上很容易使用 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> ? ...
不是 distributive在 T[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
是一个“裸”类型参数。-?
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/