《TypeScript实战指南》—2.2.7 索引类型与映射类型
2.2.7 索引类型与映射类型
索引类型与映射类型是相对复杂的内容。使用索引类型,编译器就能够检查使用了动态属性名的代码。
例如,一个 Lodash 中常见的 pluck 函数,就是从对象中选取属性的子集。下面是 pluck 函数的简化版示例:
function pluck(obj, names) {
return names.map(name => obj[name]);
}
如果需要在TypeScript中使用这个函数,要通过索引类型查询和索引访问操作符:
function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] {
return names.map(name => obj[name]);
}
interface Person {
name: string;
age: number;
}
const person: Person = {
name: 'Jarid',
age: 35
};
pluck(person, ['name']); // ["Jarid"]
pluck(person, ['profession']);
// [ts]
// Argument of type '"profession "[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type '"profession "' is not assignable to type '"name" | "age"'.
编译器会检查传入的值是否是Person属性的一部分。让我们解释一下上面代码的意义。首先看泛型,这里有T和K两种类型。根据类型推断,第一个参数obj就是person,类型会被推断为Person;而第二个数组参数的类型推断,我们可以从右往左进行阅读。keyof关键字可以获取T,也就是Person的所有属性名,即['name','age']。最后,extends关键字让泛型K继承了Person的所有属性名,即['name','age']。
依托于keyof关键字完成了类型索引。
我们再来看返回值,返回值的类型是T[K][],阅读起来有点困难。它实际表述的意思是,变量T取属性K的值的数组,其中T[K]就是索引访问操作符。
这样强大的功能保证了代码的动态性和准确性,也让代码提示变得更加丰富了:
function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] {
return names.map(name => obj[name]);
}
interface Person {
name: string;
age: number;
}
const person: Person = {
name: 'Jarid',
age: 35
};
pluck(person, ['name']); // ["Jarid"]
pluck(person, ['profession']);
// [ts]
// Argument of type '"profession"[]' is not assignable to parameter of type '("name" | "age")[]'.
// Type '"profession "' is not assignable to type '"name" | "age"'.
还有一种场景是将一个已知类型的每个属性都变为可选的,比如像这样使用问号:
interface Person {
name?: string;
age?: number;
}
在实例化 Person时,我们不必给每个属性都赋值。
想要 Person 的属性值都是只读不可修改的,如下所示:
interface Person {
readonly name: string;
readonly age: number;
}
这在JavaScript里经常会用到。
TypeScript提供了从旧类型中创建新类型的一种方式,也就是“映射类型”。在映射类型里,新类型以相同的形式去转换旧类型里的每个属性。例如,你可以令每个属性成为只读类型或可选类型。下面是一些例子:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
使用方式如下所示:
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
TypeScript 中内置了 Readonly 和 Partial,所以不需要手动声明实现。
内置的类型还有 Required、Pick、Record、Exclude、Extract、NonNullable;读者可以自己尝试这些类型方法,它们的实现都在 typescript/lib/lib.es5.d.ts 中。
- 点赞
- 收藏
- 关注作者
评论(0)