索引查询 keyof
keyof可以用于获取某种类型的所有键,其返回类型是联合类型
接口
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
类
class Person {
name: string = 'Mike';
}
let sname = keyof Person
sname = 'name'
如果把sname = 'name'
改为sname = 'yui'
的话,TypeScript 编译器会提示以下错误信息:
Type '"yui"' is not assignable to type '"name"'.
解释:
keyof Person 获取到的类型是
'name'
举个简单的例子:
let a : 1 a = 1 a = 2
以上的例子,将a的类型为1,那么这个变量的值只能是1,不能为其他的,当
a = 2
执行的时候,就会报错
基本数据类型
let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"
此外 keyof
也称为输入索引类型查询,与之相对应的是索引访问类型,也称为查找类型。在语法上,它们看起来像属性或元素访问,但最终会被转换为类型:
type P1 = Person["name"]; // string
type P2 = Person["name" | "age"]; // string | number
type P3 = string["charAt"]; // (pos: number) => string
type P4 = string[]["push"]; // (...items: string[]) => number
type P5 = string[][0]; // string
作用
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<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
在以上代码中,我们使用了 TypeScript 的泛型和泛型约束。首先定义了 T 类型并使用
extends
关键字约束该类型必须是 object 类型的子类型,然后使用keyof
操作符获取 T 类型的所有键,其返回类型是联合类型,最后利用extends
关键字约束 K 类型必须为keyof T
联合类型的子类型。
type Todo = {
id: number;
text: string;
done: boolean;
}
const todo: Todo = {
id: 1,
text: "Learn TypeScript keyof",
done: false
}
function prop<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
const id = prop(todo, "id"); // const id: number
const text = prop(todo, "text"); // const text: string
const done = prop(todo, "done"); // const done: boolean
如果访问todo
对象中不存在的属性,那么就会报错
const date = prop(todo, "date");
//Argument of type '"date"' is not assignable to parameter of type '"id" | "text" | "done"'.
typeof与keyof的结合
const COLORS = {
red: 'red',
blue: 'blue'
}
// 首先通过typeof操作符获取color变量的类型,然后通过keyof操作符获取该类型的所有键,
// 即字符串字面量联合类型 'red' | 'blue',取自于k
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
T[K] 索引访问
interface Eg1 {
name: string,
readonly age: number,
}
// string
type V1 = Eg1['name']
// string | number
type V2 = Eg1['name' | 'age']
// any
type V2 = Eg1['name' | 'age2222']
// string | number
type V3 = Eg1[keyof Eg1]
T[keyof T]
的方式,可以获取到T
所有key
的类型组成的联合类型;
T[keyof K]
的方式,获取到的是T
中的key
且同时存在于K
时的类型组成的联合类型;注意:如果
[]
中的key有不存在T中的,则是any;因为ts也不知道该key最终是什么类型,所以是any;且也会报错
& 交叉类型注意点
交叉类型取的多个类型的并集,但是如果相同key
但是类型不同,则该key
为never
interface Eg1 {
name:string,
age:number
}
interface Eg2 {
name:string,
age:string,
color: string,
}
以上两个接口定义的name
,age
属性名是相同的,但是属性的类型并不相同
type T = Eg1 & Eg2
T的类型为 {name: string; age: never; color: string}
注意,age因为Eg1和Eg2中的类型不一致,所以交叉后age的类型是never