keyof用法梳理
做了type-challenges之后,记录对keyof的理解。
# 基础概述
运算符采用keyof对象类型并生成其键的字符串或数字文字联合。
type Point = { x: number; y: number };
type P = keyof Point // 'x'|'y';
2
如果该类型具有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
2
3
4
5
6
7
8
type M 带有number类型的原因是我们在js书写对象过程中会将number类型的key自动转成string类型,所以在这里会默认带有number类型
# keyof取值
keyof 对于不同的类型将会得到不同的取值,这将关系到后续我们对keyof的理解。它会取到普通类型的原型链内的属性,对于复杂对象类型,会取到其键值。
- 使用 keyof 对 any 和 never 取值
keyof any; // string | number | symbol
keyof never; // string | number | symbol
2
- 使用 keyof 对 {}、unknown、undefined、null、object、 () => void 取值
keyof unknown; // never
keyof undefined; // never
keyof null; // never
keyof object; // never
keyof {}; // never
keyof (() => void); // never
2
3
4
5
6
- 使用 keyof 对 string 取值
keyof string;
// number | typeof Symbol.iterator | "toString" | "valueOf" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "length" | "substr" | "codePointAt" | "includes" | "endsWith" | "normalize" | "repeat" | "startsWith" | "anchor" | "big" | "blink" | "bold" | "fixed" | "fontcolor" | "fontsize" | "italics" | "link" | "small" | "strike" | "sub" | "sup"
2
- 使用 keyof 对 number 取值
keyof number; // "toString" | "toLocaleString" | "valueOf" | "toFixed" | "toExponential" | "toPrecision"
- 使用 keyof 对 symbol 取值
keyof symbol; // "toString" | "valueOf" | typeof Symbol.toPrimitive | typeof Symbol.toStringTag
- 使用 keyof 对 boolean 取值
keyof boolean; // "valueOf"
# keyof取对象值
keyof 某个对象类型,将会返回其对象类型内的键遍历,并将其作为联合类型返回
type Bar = {
name: string;
age: string;
gender: number;
};
type K = keyof Bar; // 'name'|'age'|'gender'
2
3
4
5
6
遍历 Bar 接口的属性,将得到的值是 name gender 的联合类型
当我们keyof一个数组类型时,遍历的是数组的属性,得到的就是数组属性的联合类型:
type K2 = keyof Bar[]; // "length" | "push" | "pop" | "concat" | ...
# keyof 取联合类型
当keyof取联合类型值时,取得其实是联合类型内的公共键。举例说明:
type Z = keyof ("2" | 1); // type Z = "toString" | "valueOf"
我们从上文已经可以知道取“2” 即是取其string类型里的属性,1即取其number类型内的属性。当两者联合时, keyof取的每一个联合类型内的公共属性(或是键),这时string和number原型链上有着相同的两个方法"toString" | "valueOf"就被取了出来。
那当联合类型内的元素是对象又会是怎么样呢?
type Foo = {
name: string;
age: string;
helo: string;
};
type Bar = {
name: string;
age: string;
gender: number;
};
type W = keyof (Bar | Foo);
// type W = "name" | "age"
type K = keyof (Bar | Foo | "2");
// type K = never
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我们可以看到,Foo对象和Bar对象中有着相同的属性name和age,根据上面所说的,keyof取的是联合类型共同的键,此时就会取出他们的公共属性name和age,并返回联合类型"name" | "age"。 当我们为其增加一个字符串“2”时,我们可以发现,三者之间不存在有相同的键了,所以keyof返回的是never。那么怎么样让他们都有相同的键呢,我们这样测试:
type Foo = {
name: string;
age: string;
helo: string;
toString: () => {};
};
type Bar = {
name: string;
age: string;
gender: number;
toString: () => {};
};
type K = keyof (Bar | Foo | "2"); // type K = "toString"
2
3
4
5
6
7
8
9
10
11
12
13
综上得出结论,keyof取的是联合类型共同的键
那么,keyof对于交叉类型取的又是什么呢?
# keyof取交叉类型
我们直接看例子:
type Foo = {
name: string;
age: string;
};
type Bar = {
name: string;
age: string;
gender: number;
};
type a = keyof (Foo & Bar); // "name" | "age" | "gender"
type aa = keyof Foo | keyof Bar; // "name" | "age" | "gender"
type b = keyof (Foo | Bar); // "name" | "age"
type bb = keyof Foo & keyof Bar; // "name" | "age"
// 即 ('name' | 'age') & ('name'|'age'|'gender')
type c = Exclude<a, b>; // "gender"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
可以看到,Foo & Bar 之后得到的其实是一个新的共同拥有双方键的新对象,keyof的取值遵循 keyof 取对象值的规则,将其化为键的联合类型。
当我们去掉小括号,将其通过交叉类型合并也是如此。