TypeScript的索引类型和映射类型
文章首发于:github.com/USTB-musion…
写在前面
在ts中,索引类型和映射类型是相对复杂的内容。使用索引类型,编译器就能够检查使用了动态属性名的代码,而使用映射类型,可以将旧类型转化成新类型。下面用一篇文章来稍微深入介绍一下这两个概念。
索引类型
在实际开发中,我们经常能遇到这样的场景,在对象中获取一些属性的值,然后建立对应的集合。
let person = {
name: 'musion',
age: 35
}
function getValues(person: any, keys: string[]) {
return keys.map(key => person[key])
}
console.log(getValues(person, ['name', age])) // ['musion', 35]
console.log(getValues(person, ['gender'])) // [undefined]
在上述例子中,可以看到getValues(persion, ['gender'])打印出来的是[undefined],但是ts编译器并没有给出报错信息,那么如何使用ts对这种模式进行类型约束呢?这里就要用到了索引类型,改造一下getValues函数,通过 索引类型查询和 索引访问 操作符:
function getValues<T, K extends keyof T>(person: T, keys: K[]): T[K][] {
return keys.map(key => person[key]);
}
interface Person {
name: string;
age: number;
}
const person: Person = {
name: 'musion',
age: 35
}
getValues(person, ['name']) // ['musion']
getValues(person, ['gender']) // 报错:
// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type "gender" is not assignable to type "name" | "age".
编译器会检查传入的值是否是Person的一部分。通过介绍几个概念来理解上面的代码:
1.索引类型查询操作符(keyof T)
keyof T的含义表示类型T所有公共属性的字面量的联合类型,在上述例子中,就是Person的属性名,即['name', age]
2.索引访问操作符(T[K])
T[K]表示对象T的属性K所表示的类型,在上述例子中,T[K][] 表示变量T取属性K的值的数组
3.泛型约束(K extends T)
泛型变量可以通过继承某些类型获取某些属性
介绍完这三个概念之后,应该就可以理解上面的代码了。首先看泛型,这里有T和K两种类型,根据类型推断,第一个参数person就是person,类型会被推断为Person。而第二个数组参数的类型推断(K extends keyof T),keyof关键字可以获取T,也就是Person的所有属性名,即['name', 'age']。而extends关键字让泛型K继承了Person的所有属性名,即['name', 'age']。这三个特性组合保证了代码的动态性和准确性,也让代码提示变得更加丰富了:
getValues(person, ['gender']) // 报错:
// Argument of Type '"gender"[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type "gender" is not assignable to type "name" | "age".
映射类型
一个常见的任务是将一个已知的类型每个属性都变为可选的:
interface PersonPartial {
name?: string;
age?: number;
}
在实例化Person时,我们不必给每个属性都赋值,想要一个只读版本:
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
这在JavaScript里经常出现,TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。TS内置了一些映射类型(实际上是一些语法糖),让我们可以方便地进行类型映射,可以在TypeScript包中的typescript/lib/lib.es5.d.ts中找到他们的定义: 举一些例子:
// 将传入的属性变为只读选项
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
// 将传入的属性变为可选项:keyof T 拿到 T 所有属性名, 然后 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值.
type Partial<T> = {
[P in keyof T]?: T[P];
}
使用方式如下:
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
TypeScript内置了Readonly和Partial,所以不需要手动声明实现。除此之外,还有其他常用的内置类型如:
Record
将K中的所有属性的值转化为T类型:
type Record<K extends keyof any, T> = { [P in K]: T };
Pick
从 T 中取出 一系列 K 的属性
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
Required
将传入的属性变为必选项
type Required<T> = { [P in keyof T]-?: T[P] };
参考链接
你可以关注我的公众号「慕晨同学」,鹅厂码农,平常记录一些鸡毛蒜皮的点滴,技术,生活,感悟,一起成长。
转载自:https://juejin.cn/post/6844904061783769095