TypeScript的"keyof"类型操作符详解

458 阅读2分钟

以下内容是官方文档相关翻译:

对一个对象类型使用 keyof 操作符,会返回该对象属性名组成的一个字符串或者数字字面量的联合。这个例子中的类型P 就等同于"x" | "y"

type Point = { x: number; y: number };
type P = keyof Point;
​
// type P = keyof Point

但如果这个类型有一个 string 或者 number 类型的索引签名,keyof 则会直接返回这些类型:

type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish;
// type A = number
​
type Mapish = { [k: string]: boolean };
type M = keyof Mapish;
// type M = string | number

注意在这个例子中,Mstring | number,这是因为 JavaScript 对象的属性名会被强制转为一个字符串,所以 obj[0]obj["0"] 是一样的。

到这里,官方介绍结束。

接下来,我们想要理解keyof的原理。首先需要理解字面量类型(literal types)联合字面量类型(union of literal types)

字面量类型 & 联合字面量类型

TS中,字面量类型是更具体的string,numberboolean类型。比如"Hello World"是一个string,但是string类型不是"Hello World"

type Animal = "dog"

这样意味着Animal类型只有一个人字符串的值"dog",没有其他string类型的值。

type Animal = "dog" | "cat" | "elephant"

现在,Animal类型对象的值可以是"dog""cat""elephant"

使用keyof

值得注意的点,keyof可以返回一个数字字面量的联合类型。比如有一个类型Tkeyof T将会得到一个新的联合字面量类型。组成它的字面量类型是T的属性名称,最后生产的类型是String的子类型。 举个例子:

interface Person {
  name: string
  age: number
  gender: string
}
​
type NewType = keyof Person

这里, "keyof Person"会得到一个新类型, NewType是联合字面量类型("name" | "age" | gender)。

let newObj: NewType;
newObj = "name"; // ok
newObj = "age"; // ok
newObj = "gender"; // ok
newObj = "height"; // Type '"height"' is not assignable to type 'keyof Person'.

keyof typeof同时使用(扩展内容)

单独使用keyof,是因为上面的例子中,我们已经知道Person的类型。只需要对Person使用keyof即可。

const example = { name: "leo", age: 20, gender: "male"}

这时候,就需要keyof typeof一起使用。

typeof example 会得到对象的类型 { name: string, age: number, gender: string }

type LiteralType = keyof typeof example;
let obj: LiteralType;
obj = "name"; // ok
obj = "age"; // ok
obj = "height"; // Type '"height"' is not assignable to type '"name" | "age" | "gender"'.

在TS中,enum下编译时被用作类型,来实现常量的类型安全。但是枚举在运行时被视为对象。这是因为,当TS代码被编译为JS的时候,他们会被转换成普通的对象。我们来看下面的枚举:

enum Colors {
  white = '#FFFFFF',
  red = '#FF0000',
}

上述代码的Colors在运行时是作为一个对象存在,不是一个类型,因此,需要使用"keyof typeof"

type ColorsKey = keyof typeof Colors;
let key: ColorKeys;
key = 'white'; // ok
key = 'red'; // ok
key = 'blue'; // Type '"blue"' is not assignable to type '"white" | "black" | "red"'.