01 · NOTES / WebGIS 开发
第 38 节 · 工具类型全解
2026 年 9 月 1 日
第 38 节 · 工具类型全解
📌 版本信息:基于 TypeScript 5.x 内置 Utility Types(2026-08-29 核对) 📚 来源:TS 手册 · Utility Types | 阮一峰 TS 教程 · 工具类型
一、这一节的目标
- 精通六大高频工具类型:Partial / Required / Pick / Omit / Record / Readonly
- 精通函数相关三件:ReturnType / Parameters / Awaited
- 掌握映射类型的语法原理(工具类型的"发动机")
- 会组合工具类型解决真实建模需求
二、对象改造五件套
以 interface MapConfig { center: [number, number]; zoom: number; basemap: string; maxZoom?: number } 为例:
// Partial<T>:所有字段变可选 —— "更新函数"的参数标配
function updateConfig(patch: Partial<MapConfig>) { /* 传哪个改哪个 */ }
updateConfig({ zoom: 12 });
// Required<T>:所有字段变必填(含原本可选的)
type StrictConfig = Required<MapConfig>; // maxZoom 也必须给
// Readonly<T>:所有字段只读
const frozen: Readonly<MapConfig> = { center: [30, 114], zoom: 10, basemap: 'osm', maxZoom: 18 };
// frozen.zoom = 12; // ❌ Cannot assign to 'zoom'
// Pick<T, K>:挑出部分字段
type ZoomOnly = Pick<MapConfig, 'zoom' | 'maxZoom'>; // { zoom; maxZoom? }
// Omit<T, K>:排除部分字段(Pick 的反面)
type NoBasemap = Omit<MapConfig, 'basemap'>;
记忆法:Partial/Required/Readonly 是"全员改造";Pick/Omit 是"字段增删"——gis 数据流转中 Update 用 Partial、对外导出用 Omit、内部主类型用 interface。
三、Record 与键值映射
// Record<K, V>:以 K 的每个值为键、V 为值的对象类型
type LayerType = 'base' | 'vector' | 'label';
const defaultVisibility: Record<LayerType, boolean> = {
base: true, vector: true, label: false,
}; // ✅ 三个键必须齐全——联合类型的"全覆盖"强制器
// 从映射对象取值(键收窄)
const colors: Record<LayerType, string> = { base: '#fff', vector: '#f00', label: '#00f' };
const t: LayerType = 'vector';
colors[t]; // string(TS 知道 t 只能是那三个键)
💡 GIS 场景:按图层类型配默认样式(
Record<LayerType, StyleConfig>)、按要素类型分组的统计表——Record + 字面量联合是"配置表"的标准建模。
四、函数三件套
// Parameters<T>:取函数的参数元组类型
type FetchFn = typeof fetchQuakes; // (url: string) => Promise<QuakeCollection>
type FetchArgs = Parameters<FetchFn>; // [url: string]
// ReturnType<T>:取返回值类型
type QuakeData = ReturnType<FetchFn>; // Promise<QuakeCollection>
type QuakeDataSync = Awaited<QuakeData>; // QuakeCollection(把 Promise 拆开!)
// Awaited<T>:解包 Promise(含嵌套 Promise<Promise<T>>)
// ⚠️ 实战区别:ReturnType<typeof asyncFn> 拿到的是 Promise<T>;
// 要 T 本身必须 Awaited<ReturnType<typeof asyncFn>> —— 忘了 Awaited 是高频小坑
为什么这三件重要:类型跟着实现走——函数改了签名,依赖 typeof fn 的下游类型自动更新,不用手工同步。
五、映射类型:工具类型的发动机
工具类型都是"遍历字段加工"的语法糖,理解它你就能自造:
// 通用形状:{ [K in keyof T]: 加工后的类型 }
type MyPartial<T> = { [K in keyof T]?: T[K] }; // Partial 的真身(? = 可选)
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
// 自造:把所有函数字段包一层 Promise
type Asyncify<T> = { [K in keyof T]: Awaited<ReturnType<T[K]>> };
// 修饰符增删:-? 去可选、-readonly 去只读
type Writable<T> = { -readonly [K in keyof T]: T[K] };
keyof T 取字段名联合('center' | 'zoom' | …);T[K] 取字段类型。读任何花哨的类型定义,先找 [K in keyof T] 这台发动机。
六、动手建模(无独立练习,融进 C-02b)
综合项目 C-02b 里会用到本节组合:
// 状态更新用 Partial
interface AppState { quakes: QuakeFeature[]; loading: boolean; error?: string }
type AppPatch = Partial<AppState>;
// 表格列配置用 Record
const columns: Record<'place' | 'mag' | 'depth', { label: string; width: number }> = {
place: { label: '地点', width: 200 },
mag: { label: '震级', width: 80 },
depth: { label: '深度', width: 80 },
};
// 新增一列时 Record 会强制你补配置——漏了直接编译报错
七、自测题
Partial和Required的关系?各自典型场景?Pick与Omit谁更适合"多数字段都要、去掉一两个"?Record<LayerType, X>相比{ [k: string]: X }强在哪?ReturnType<typeof fetchUser>拿到 Promise,要 User 怎么写? - 写出
MyOmit<T, K>的映射类型实现(提示:keyof T 排除 K——Exclude)。
参考答案
- 互为反面(全员可选 ⇄ 全员必填);Partial 用于更新补丁/配置覆盖,Required 用于校验完整形态(如初始化完成后)。
- 字段多时用 Omit 只列排除项;字段少时用 Pick 列保留项——哪个列举得少用哪个。
- 键被锁死为 LayerType 联合:必须全覆盖、不能多键、点访问时键名有自动补全与检查。
Awaited<ReturnType<typeof fetchUser>>。type MyOmit<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P] };
八、下一步
TS 体系只剩最后一块 → 第 39 节:声明与模块(d.ts、declare、第三方类型),阶段二 TS 部分收官。
TAGSweb