你用过上图中 Partial、Required、Record 和 Pick 这些工具类型么?在这些工具类型内部都使用了 keyof 操作符,那么该操作符的作用是什么?如果不清楚的话,阅读完本期的内容,也许你就懂了。
在 JavaScript 中,我们可以通过 Object.keys 方法来获取对象中的键,返回的是键组成的数组。
const user = {
id: 666,
name: "阿宝哥",
}
const keys = Object.keys(user); // ["id", "name"]
- 1.
- 2.
- 3.
- 4.
- 5.
而在 TypeScript 中,我们面对的是类型。如果要获取对象类型中的键,就需要使用 keyof 操作符。该操作符是在 TypeScript 2.1 版本中引入的,用于获取某种类型中的所有键,其返回类型是联合类型。
type User = {
id: number;
name: string;
}
type UserKeys = keyof User; // "id" | "name"
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
在获取对象类型的键之后,我们就可以通过类似属性访问的语法来访问该键对应的值的类型。
type U1 = User["id"] // number
type U2 = User["id" | "name"] // string | number
type U3 = User[keyof User] // string | number
- 1.
- 2.
- 3.
那么在实际工作中,keyof 操作符有什么用呢?这里我们来举一个例子。
这是一个简单的 getProperty 函数,它接收 obj 和 key 两个参数,用于获取 obj 对象上 key 参数对应的属性值。
function getProperty(obj, key) {
return obj[key];
}
const user = {
id: 666,
name: "阿宝哥",
}
const userName = getProperty(user, "name");
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
那么在 TS 中如何定义上述的 getProperty 函数呢?这里我们直接把该函数复制到 TS 项目中。对于上述的代码,TS 编译器会提示以下错误信息:
参数“obj”隐式具有“any”类型。ts(7006)
参数“key”隐式具有“any”类型。ts(7006)
- 1.
- 2.
该信息告诉我们 obj 和 key 参数隐式具有 "any" 类型。要解决该问题,我们可以显式定义 obj 和 key 参数的类型。
function getProperty(obj: object, key: string) {
return obj[key]; // Error
}
- 1.
- 2.
- 3.
设置之后参数上的错误消息消失了,但函数体中又出现了新的错误信息:
元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{}"。
在类型 "{}" 上找不到具有类型为 "string" 的参数的索引签名。ts(7053)
- 1.
- 2.
那么又该如何解决上述问题呢?这时我们可以使用 TS 泛型和本期的主角 keyof 操作符:
function getProperty<T extends object, K extends keyof T>(
obj: T, key: K
) {
return obj[key];
}
- 1.
- 2.
- 3.
- 4.
- 5.
在以上代码中,我们定义了两个类型变量 T 和 K。对于类型变量 T 使用 extends 约束该类型变量对应的实际类型必须是 object 类型的子类型。而类型变量 K 也使用 extends 约束该类型变量对应的实际类型为对象类型所有键组成的联合类型的子类型。
之后,利用 getProperty 函数,我们就可以获取指定属性的值。当 key 对应的属性不存在时,TS 将会提示相应的错误。
const userId = getProperty(user, "id"); // Ok
const userName = getProperty(user, "name"); // Ok
const userAge = getProperty(user, "age"); // Error
- 1.
- 2.
- 3.
keyof 操作符不仅可以应用于对象类型,也可以应用在基本数据类型、any 类型、类和枚举类型上。
type K1 = keyof boolean; // "valueOf"
type K2 = keyof number; // "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
type K3 = keyof any; // string | number | symbol
class Person {
id: number = 666;
name: string = "阿宝哥";
}
type P = keyof Person; // "id" | "name"
enum HttpMethod {
Get,
Post,
}
type Method = keyof typeof HttpMethod; // "Get" | "Post"
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
keyof 操作符的另一个常见用途是映射类型,关于映射类型的相关知识点,阿宝哥将在后面的文章中单独介绍。
阅读完本文之后,你应该就知道 TS 内置工具类型中 keyof 操作符的作用了。