TypeScript keyof 操作符
使用object(key)获取对象属性时报错
Typescript - 类型操作符
keyof 用于遍历某种类型的属性(可以操作接口、类以及基本数据类型)
在 TS 中定义一个获取函数属性的函数如下:
function prop(obj: object, key: string) {
return obj[key];
}
在上面代码中,为了避免调用 prop 函数时传入错误的参数类型,我们为 obj 和 key 参数设置了类型,分别为 {} 和 string 类型。然而,事情并没有那么简单。针对上述的代码,TypeScript 编译器会输出以下错误信息:
Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type ‘{}’.
元素隐式地拥有 any 类型,因为 string 类型不能被用于索引 {} 类型。要解决这个问题,你可以使用以下非常暴力的方案:
function prop(obj: object, key: string) {
return (obj as any)[key];
}
该函数用于获取某个对象中指定属性的属性值。因此我们期望用户输入的属性是对象上已存在的属性,那么如何限制属性名的范围呢?这时我们可以利用本文的主角 keyof 操作符:
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
在以上代码中,我们使用了 TypeScript 的泛型和泛型约束。首先定义了 T 类型并使用 extends 关键字约束该类型必须是 object 类型的子类型,然后使用 keyof 操作符获取 T 类型的所有键,其返回类型是联合类型,最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型。
在使用对象的数值属性时,我们也可以使用 keyof 关键字。请记住,如果我们定义一个带有数值属性的对象,那么我们既需要定义该属性,又需要使用数组语法访问该属性:
class ClassWithNumericProperty {
[1]: string = "Semlinker";
}
let classWithNumeric = new ClassWithNumericProperty();
console.log(`${classWithNumeric[1]} `);
下面我们来举个示例,介绍一下在含有数值属性的对象中,如何使用 keyof 操作符来安全地访问对象的属性:
enum Currency {
CNY = 6,
EUR = 8,
USD = 10
}
const CurrencyName = {
[Currency.CNY]: "人民币",
[Currency.EUR]: "欧元",
[Currency.USD]: "美元"
};
console.log(`CurrencyName[Currency.CNY] = ${CurrencyName[Currency.CNY]}`);
console.log(`CurrencyName[36] = ${CurrencyName[6]}`);
为了方便用户能根据货币类型来获取对应的货币名称,我们来定义一个 getCurrencyName 函数,具体实现如下:
function getCurrencyName<T, K extends keyof T>(key: K, map: T): T[K] {
return map[key];
}
console.log(`name = ${getCurrencyName(Currency.CNY, CurrencyName)}`);
同样,getCurrencyName 函数和前面介绍的 prop 函数一样,使用了泛型和泛型约束,从而来保证属性的安全访问。
typeof 操作符用于获取变量的类型。因此这个操作符的后面接的始终是一个变量,且需要运用到类型定义当中。为了方便大家理解,我们来举一个具体的示例:
type Person = {
name: string;
age: number;
}
let man: Person = {
name: "Semlinker",
age: 30
}
type Human = typeof man;
const COLORS = {
red: 'red',
blue: 'blue'
}
// 首先通过typeof操作符获取color变量的类型,然后通过keyof操作符获取该类型的所有键,
// 即字符串字面量联合类型 'red' | 'blue'
type Colors = keyof typeof COLORS
let color: Colors;
color = 'red' // Ok
color = 'blue' // Ok
// Type '"yellow"' is not assignable to type '"red" | "blue"'.
color = 'yellow' // Error
如果直接 keyof 一个对象变量的话,获取到的并不是该对象的属性组成的联合类型,而会是该对象的方法及属性的联合类型,比如除了属性还会有 “valueOf” “toString” 等。
最后留到思考题,有兴趣的小伙伴可以想一想:
interface StringIndexArray {
[index: string]: string;
}
interface NumberIndexArray {
[index: number]: string;
}
type K1 = keyof StringIndexArray // type K1 = string | number
type K2 = keyof NumberIndexArray // type K2 = number
Keyof inferring string | number when key is only a string