Typescript:如何声明一种值等于对象键的数组类型?

Posted

技术标签:

【中文标题】Typescript:如何声明一种值等于对象键的数组类型?【英文标题】:Typescript: How to declare a type of array whose values are equal to keys of object? 【发布时间】:2021-11-06 21:40:39 【问题描述】:

如何在这段代码中定义MyInterfaceKeys

interface MyInterface extends Record<string, any> 
   Appearance?: "default" | "primary" | "link";
   Size?: "small" | "medium" | "large";
   Color?: string;
   Block?: boolean;


type MyInterfaceKeys = (keyof MyInterface)[]

// ok => MyInterfaceKeys === ["Block", "Color"] 
// ok => MyInterfaceKeys === ["Appearance"]

// Error => MyInterfaceKeys === ["UnknownKey"]

其实我是想把对象 props 转换成 union 字面量:

type MyInterfaceKeys = ("Appearance" | "Size" | "Color" | "Block")[]

【问题讨论】:

您的代码似乎运行良好 - typescriptlang.org/play?#code/…,有什么问题? 看编辑的 keyof MyInterface 完全等同于联合文字 - typescriptlang.org/play?#code/…。 我发现了问题所在,我的界面上有extends Record&lt;string, any&gt;这一行。 【参考方案1】:

TypeScript 中的对象类型有一组已知键,它们对应于单个字符串或数字literals(或符号);和一组index signature 键,它们一次对应多个可能的键(这些过去只是stringnumber,但现在你也可以使用pattern template literals and symbols in index signatures)。例如下面的类型:

type Foo = 
    [k: string]: 0 | 1
    x: 0,
    y: 1

有两个已知密钥"x""y" 和一个索引签名密钥string。您的MyInterface 类型有四个已知密钥,但它也有一个string 索引签名密钥(因为它扩展了Record&lt;string, any&gt;,它有一个string 索引签名密钥)。


The keyof operator 产生所有键的union。对于Foo,即"x" | "y" | string,对于MyInterface,即"Appearance" | "Size" | "Color" | "Block" | string

由于每个字符串文字(如"x""y")都是string 的子类型,因此字符串文字类型与string 的并集就是string,编译器急切地将其简化为这个。所以keyof Fookeyof 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

【讨论】:

太完美了:)))

以上是关于Typescript:如何声明一种值等于对象键的数组类型?的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法将类型声明中特定键的值用作同一声明中另一个键的类型(在 Typescript 中)?

TypeScript 中每个键的泛型映射对象类型

Typescript - 如何声明具有已知属性的匿名对象?

Javascript条件内部TypeScript接口

如何在 TypeScript 中声明仅包含对象而不包含函数的类型

如何仅使用 TypeScript 声明其属性为字符串类型的对象?