我觉得这应该没有那么难。然而,无论我怎么尝试,我都不能让它工作。这是目前为止我找到的最好的:
// Sample type. I want to make this into 'a' | 'b' | 'x' | 'c' | 'd'
type O = Record<'a', Record<'b' | 'x', Record<'c' | 'd', string | number>>>;
// Explicit way of doing it, with limited depth
type Explicit = keyof O | keyof O[keyof O] | keyof O[keyof O][keyof O[keyof O]];
// What I hoped would work
type ExtractKeys<T> = T extends Record<infer U, any> ?
keyof T | ExtractKeys<T[U]> :
never;
// Or
type ExtractKeys2<T> = T extends Record<string, any> ?
keyof T | ExtractKeys<T[keyof T]> :
never;
// Try it
// TS2589: Type instantiation is excessively deep and possibly infinite.
const tryIt: ExtractKeys<O> = 'a';
// Can assign anything
const tryIt2: ExtractKeys2<O> = 'z';
错误很明显,我以无限递归结束。但我真的看不出是怎么回事?我也找不到更好的办法。什么好主意吗?
游乐场
您在EtractKeys2
中有一个错别字。您使用EtractKeys
而不是ExtractKeys2
进行递归调用。
type ExtractKeys2<T> = T extends Record<string, any>
? keyof T | ExtractKeys2<T[keyof T]>
: never
const tryIt2: ExtractKeys2<O> = 'z';
可以运行了
由于infer U
,您的第一个方法失败了。这个检查实际上返回原语的true
。
type Test1 = number extends Record<string, any> ? true : false
// ^? false
type Test2 = number extends Record<infer U, any> ? true : false
// ^? true
由于这始终是true
,因此最终会出现一个无限循环,导致类型实例化过于深入且可能无限错误。
有趣的是,你最终得到了keyof Number
。
type Test3 = number extends Record<infer U, any> ? U : false
// ^? keyof Number
游乐场
根据我的理解,您希望有一个返回给定对象类型及其对象属性的所有键的类型。
如果是这种情况,下面可能是一种方法:
export type Keys<T extends object> = {
[K in keyof T]: T[K] extends object ? Keys<T[K]> | K : K
}[keyof T]
interface User {
name: {
first: string
last: string
}
}
const x: Keys<User> = 'first' // 'last', 'name'
type O = Record<'a', Record<'b' | 'x', Record<'c' | 'd', string | number>>>;
const o: Keys<O> = 'a' // 'b', 'c', 'd', 'x'
这里,我将T
的每个键映射到键类型K
本身。如果T[K]
的值(即键的值)应该是一个对象,则创建嵌套对象的键和前键K
本身的联合。
[K in keyof T]: T[K] extends object ? Keys<T[K]> | K : K
最后,使用
生成所有提到的嵌套键联合的联合:[keyof T]