TypeScript 中的对象类型有一组已知键,它们对应于单个字符串或数字literals(或符号);和一组index signature 键,它们一次对应多个可能的键(这些过去只是string 或number,但现在你也可以使用pattern template literals and symbols in index signatures)。例如下面的类型:
type Foo = {
[k: string]: 0 | 1
x: 0,
y: 1
}
有两个已知密钥"x" 和"y" 和一个索引签名密钥string。您的MyInterface 类型有四个已知密钥,但它也有一个string 索引签名密钥(因为它扩展了Record<string, any>,它有一个string 索引签名密钥)。
The keyof operator 产生所有键的union。对于Foo,即"x" | "y" | string,对于MyInterface,即"Appearance" | "Size" | "Color" | "Block" | string。
由于每个字符串文字(如"x" 和"y")都是string 的子类型,因此字符串文字类型与string 的并集就是string,编译器急切地将其简化为这个。所以keyof Foo 和keyof MyInterface 就是string。从某种意义上说,string“吸收”了所有的字符串文字键。
因此,如果有任何索引签名密钥吸收已知密钥,则您不能使用 keyof 来获取已知密钥。
那么,你能做什么?您可以做的最干净的事情是考虑重构您的代码,以便可以更轻松地捕获您想要的信息:
interface MyKnownInterface {
Appearance?: "default" | "primary" | "link";
Size?: "small" | "medium" | "large";
Color?: string;
Block?: boolean;
}
interface MyInterface extends Record<string, any>, MyKnownInterface { }
type KK = (keyof MyKnownInterface) & string
// type KK = "Appearance" | "Size" | "Color" | "Block"
type MyInterfaceKeys = (keyof MyKnownInterface)[]
// type MyInterfaceKeys = (keyof MyKnownInterface)[]
// type MyInterfaceKeys = ("Appearance" | "Size" | "Color" | "Block")[]
这里的MyInterface 与之前的相同,但keyof MyKnownInterface 是您想要的已知键的并集。
您也可以使用类型操作来尝试梳理出已知的键,而不是仅使用 keyof,尽管我认为我知道的所有可能的方式都有一些奇怪的边缘情况(我即将提交一个错误与symbol 键有关)。但希望您不会遇到这种极端情况(MyInterface 示例不会)。
一种方法是使用key remapping in mapped types 来抑制索引签名,它可以被识别为任何不需要值的键,即使它不是可选的(索引签名表示任意数量的正确类型的键,包括零这样的键):
type KnownKeys<T> = keyof {
[K in keyof T as {} extends { [P in K]: any } ? never : K]: never
}
如果我们在你的情况下这样做,你会得到:
type MyInterfaceKeys = (KnownKeys<MyInterface>)[]
// type MyInterfaceKeys = ("Appearance" | "Size" | "Color" | "Block")[]
Playground link to code