TypeScript 映射类型
映射类型通过 in keyof 遍历一个类型的全部键,对每个键套用统一规则,批量生成新类型的属性,相当于「把类型当作数据来加工」,比逐个手写新接口高效得多,是大量工具类型背后的统一套路。
基本语法
用 [K in keyof T] 遍历 T 的所有键,配合索引访问 T[K] 读取原属性类型:
interface User {
name: string;
age: number;
}
type Flags<T> = { [K in keyof T]: boolean };
type UserFlags = Flags<User>;
// type UserFlags = { name: boolean; age: boolean }
const f: UserFlags = { name: true, age: false };
console.log(f); // 输出:{ name: true, age: false }
keyof T 提供键集合,T[K] 取出对应值类型,二者组合即可变换任意结构。
修饰符的增删
映射时可追加 readonly、?,用 - 前缀移除修饰:
interface Todo {
title: string;
done: boolean;
}
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // 去掉只读
type Optional<T> = { [K in keyof T]?: T[K] }; // 全部变可选
type ReadonlyAll<T> = { readonly [K in keyof T]: T[K] }; // 全部只读
interface Fixed {
readonly id: number;
name: string;
}
const m: Mutable<Fixed> = { id: 1, name: "x" };
m.id = 2; // 合法:readonly 已被移除
+readonly、+? 表示添加(默认就是添加),-readonly、-? 表示移除。
内置工具类型的原型实现
Partial、Readonly、Required、Pick 都可以用映射类型一行写出:
type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
type MyRequired<T> = { [K in keyof T]-?: T[K] };
type MyPick<T, Keys extends keyof T> = { [K in Keys]: T[K] };
interface User {
name: string;
age: number;
}
const p: MyPartial<User> = { name: "张三" }; // 只给部分字段
const r: MyRequired<MyPartial<User>> = { name: "张三", age: 30 };
理解了映射语法,官方工具类型的源码就一目了然。
键重映射 as
TS 4.1 起可在键后加 as 新键,按规则改名或过滤键,甚至拼出模板字符串键名:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Point {
x: number;
y: number;
}
// type Getters<Point> = { getX: () => number; getY: () => number }
const g: Getters<Point> = { getX: () => 1, getY: () => 2 };
// as 分支产出 never 即删除该键
type NoDone<T> = { [K in keyof T as K extends "done" ? never : K]: T[K] };
type N = NoDone<{ a: number; done: boolean }>; // type N = { a: number }
键重映射让映射类型从「改类型」升级到「改结构、改键名」,能力大增。
小结:映射类型 = [K in keyof T] 遍历 + 修饰符增删 + T[K] 取原值,配合 as 还能重命名键;Partial、Pick 等大批工具类型都建立在它之上,掌握它等于掌握类型变换的核心。