key in
假如我们有一个非常复杂的API返回结果对象:
type APIResponse = {
user: {
userId: string;
friendList: {
count: number;
friends: {
firstName: string;
lastName: string;
}[];
};
};
};
我们希望使用friendList
对象,我们可能会想将friendList
抽离为一个单独的类型:
type FriendList = {
count: number;
friends: {
firstName: string;
lastName: string;
}[];
};
type APIResponse = {
user: {
userId: string;
friendList: FriendList;
};
};
如果我们又想使用friends
中的对象,我们可能又需要再单独抽离一个类型。这种方式就显得比较繁琐。
其实我们不需要一个个的抽离类型,通过使用key in
的方式可以直接获取类型中某些成员类型:
type FriendList = APIResponse['user']['friendList']
如果我们想获取数组中的对象:
type Friend = FriendList['friends'][number]
number
并不是唯一可用于获取数组子项类型的方式。还可以使用诸如0
,1
或者其他数值字面量。
key in
的方式和我们编程中获取对象中属性的方式十分相似。但ts只支持数组形式获取成员类型。不支持.
语法,如APIResponse.user
是违法的。
Keyof
keyof
用于获取对象属性名,并用这些属性名构成联合类型:
type ResponseKeys = keyof APIResponse; // 'user'
type UserKeys = keyof APIResponse['user']; // 'userId' | 'friendList'
type FriendListKeys = keyof APIResponse['user']['friendList']; // 'count' | 'friends'
如果操作的对象是一个类,则会获取该类所有的public
属性名:
class Person {
name = "st";
age = 12;
publicMethod() {}
private privateMethod() {}
protected protectedMethod() {}
}
type Keys = keyof Person; // "name" | "age" | "publicMethod"
通过key in
和keyof
,我们可以安全的获取对象中的某一个属性的值:
function get<O extends object, K extends keyof O>(o: O, k: K): O[K] {
return o[k];
}

name
或者age
字符串。
如果我们想更深层的获取对象属性值,可结合函数重载:
type Get = {
<O extends object, K1 extends keyof O>(o: O, k1: K1): O[K1];
<O extends object, K1 extends keyof O, K2 extends keyof O[K1]>(
o: O,
k1: K1,
k2: K2
): O[K1][K2];
<
O extends object,
K1 extends keyof O,
K2 extends keyof O[K1],
K3 extends keyof O[K1][K2]
>(
o: O,
k1: K1,
k2: K2,
k3: K3
): O[K1][K2][K3];
};
let get: Get = (obj: any, ...keys: string[]) => {
let result = obj;
keys.forEach((k: string) => (result = result[key]));
return result;
};
get({ events: { id: 1, info: { name: "name" } } }, "events", "info", "name");
如果keyof
操作的类型中存在可索引属性,情况会稍有不同。此时keyof
返回的属性名类型会进行范围扩张。
如果可索引属性名类型为string
:
interface Person {
name: string;
age: number;
// key可以使用任意string或number类型扩张(number类型属性最后也会被转化为string使用)
[key: string]: any;
}
type Keys = keyof Person; // string | number
如果可索引属性名类型为number
:
interface Person {
name: string;
age: number;
// key只能使用number类型属性扩张
[key: number]: any;
}
type Keys = keyof Person; // number | "name" | "age"
在ts中,对象或者数组的属性名可以为字符串,数值或者symbol
。所以使用keyof
最大会扩张为string | number | symbol
。如果开启keyofStringsOnly
则只会扩张为string
。
// 开启前
type Keys = keyof any; // string | number | symbol
// 开启后
type Keys = keyof any; // string