返回笔记列表
01 · NOTES / WebGIS 开发

第 38 节 · 工具类型全解

2026 年 9 月 1 日

第 38 节 · 工具类型全解

📌 版本信息:基于 TypeScript 5.x 内置 Utility Types(2026-08-29 核对) 📚 来源:TS 手册 · Utility Types阮一峰 TS 教程 · 工具类型

一、这一节的目标

  1. 精通六大高频工具类型:Partial / Required / Pick / Omit / Record / Readonly
  2. 精通函数相关三件:ReturnType / Parameters / Awaited
  3. 掌握映射类型的语法原理(工具类型的"发动机")
  4. 会组合工具类型解决真实建模需求

二、对象改造五件套

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 会强制你补配置——漏了直接编译报错

七、自测题

  1. PartialRequired 的关系?各自典型场景?
  2. PickOmit 谁更适合"多数字段都要、去掉一两个"?
  3. Record<LayerType, X> 相比 { [k: string]: X } 强在哪?
  4. ReturnType<typeof fetchUser> 拿到 Promise,要 User 怎么写?
  5. 写出 MyOmit<T, K> 的映射类型实现(提示:keyof T 排除 K——Exclude)。

参考答案

  1. 互为反面(全员可选 ⇄ 全员必填);Partial 用于更新补丁/配置覆盖,Required 用于校验完整形态(如初始化完成后)。
  2. 字段多时用 Omit 只列排除项;字段少时用 Pick 列保留项——哪个列举得少用哪个。
  3. 键被锁死为 LayerType 联合:必须全覆盖、不能多键、点访问时键名有自动补全与检查。
  4. Awaited<ReturnType<typeof fetchUser>>
  5. type MyOmit<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P] };

八、下一步

TS 体系只剩最后一块 → 第 39 节:声明与模块(d.ts、declare、第三方类型),阶段二 TS 部分收官。

TAGSweb