# 映射类型 (mapped type )
映射类型通过迭代联合或者字面量类型来创建新的对象类型。
# in
在 JavaScript in (opens new window) 用于判断对象或原型链是否包含某属性,也用于for in 的循环语句中迭代对象的键名。TypeScript 借鉴了此概念采用 in 操作符,在属性申明的模板上迭代属性名映射新的类型。
// 创建一个对象类型, 属性 a,b 都是字符串类型
type ObjAB = {
// in 迭代字符串字面量类型的值作为属性名,并设定属性为 string 类型
[key in 'a' | 'b']: string;
};
// 合法, 对象包含 a,b 且为字符串类型
let objAB: ObjAB = {
a: 'foo',
b: 'bar'
};
除了迭代字符串字面量,也可迭代数值字面量。
//限定索引前三个元素必须为 数值
type InNumber = {
[key in 0 | 1 | 2]: number;
};
// 合法,只限定前 3 个元素
let inNumberArr: InNumber = [1, 2, 3, 'df'];
也可迭代枚举类型,注意属性名为枚举类型标识符的值而标识符的名称。
enum EnumType {
Foo = 'foo',
Bar = 1,
FooBar
}
// 注意 a 的键名为枚举类型的值而非标识符名称。
let a: { [key in EnumType]: string } = {
foo: 'foo',
1: 'bar',
2: 'foo'
};
一个典型的用例是结合泛型,来动态创建类型。
// 采用泛型映射 K 类型的键名为 T 类型
type MapK<K extends string, T> = {
readonly [key in K]: T;
};
type PageInfo = {
title: string;
};
type Pages = 'home' | 'about';
// 迭代 Pages 字面量类型的值作为键名,并要求值为 PageInfo 类型
let pages: MapK<Pages, PageInfo> = {
home: {
title: 'home'
},
about: {
title: 'about'
}
};
这正是 TypeScript 内部提供的 Record<K,T> (opens new window) 的原理。
进一步阅读查看 mapped types (opens new window)
# 结合 keyof
结合索引类型查询 keyof,你可创造更多复杂的类型模板。
/**
定义对象泛型 T 的所有属性为只读,产生一个只读的对象类型
1. keyof T 提取泛型 T 的键名为字面量类型
2. P in keyof T 遍历字面量类型的值
3. readonly [P in keyof T] 修饰键名为只读
4. T[P] 映射每个键名对应值的类型
*/
type ReadOnlyObj<T> = {
readonly [P in keyof T]: T[P];
};
interface Obj1 {
foo: number;
bar: string;
}
// 限定 readOnlyObj 的对象类型为只读的 Obj1
let readOnlyObj: ReadOnlyObj<Obj1> = {
foo: 12,
bar: 'bar'
};
// 报错, 只读对象不允许修改
readOnlyObj.foo = 13;
# 数组或元祖使用
可以利用 in 迭代数组结合之前的操作符,可产生类似数组的 map 方法,用于创建新的类型。
type MapToPromise<T> = {
[key in keyof T]: Promise<T[key]>;
};
type Coordinate = [number, number];
type PromiseCoordinate = MapToPromise<Coordinate>;
// 设置 promiseAll resolve 的值必须为 number 类型的 promise 数组
let promiseAll: PromiseCoordinate = [Promise.resolve(1), Promise.resolve(2)];
示例利用 in 结合索引类型,将元祖类型装换为了包含 Promise 的元祖。
延伸阅读 github issues Mapped Types (opens new window)
TIP
TypeScript 内置了一套类型模板,详见 Utility Types (opens new window)
# 映射类型修饰符
对象类型中,可通过 readonly,? 属性修饰符表示只读和可选。
在映射类型中可利用 -,+ 表示删除或者添加的只读或可选修饰符。
// 删除泛型对象的只读和可选修饰符
type Writable<T> = {
-readonly [K in keyof T]-?: T[K];
};
interface ReadonlyObj {
readonly foo: string;
readonly bar?: number;
}
// 由于删除可选, bar 属性必须存在
let writeObj: Writable<ReadonlyObj> = {
foo: 'foo',
bar: 12
};
// 合法, 删除了只读修饰符
writeObj.foo = 'bar';
writeObj.bar = 12;
详细资料参阅 Improved control over mapped type modifiers (opens new window)