三块面板:TS 源码(含手写映射类型)/ 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。工具类型本质都是映射类型「遍历键联合逐个生成属性」的组装。
interface User { id: number; name: string; pwd: string }
type Upd = Partial<User> // 全部加 ?
type MyPartial<T> = { [K in keyof T]?: T[K] } // 手写:[K in keyof T]? 就是逐键加 ?
type Public = Pick<User, 'id' | 'name'> // 挑键
type MyPick<T, K extends keyof T> = { [P in K]: T[P] } // 手写:映射源是要保留的键
extends keyof T 约束,只能填真实存在的键。'token' 不在 User 上,越界即报错——这正是 Pick 的安全价值。类型擦除后,下面这段等价 JS 演示的只是「运行时对对象的操作」,跟 Partial/Pick 无关——这恰好印证工具类型不产出任何代码。
type A = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
type MyExclude<T, U> = T extends U ? never : T // 手写:命中就丢(条件类型分发)
type C = NonNullable<string | null | undefined> // string
type MyNonNullable<T> = T extends null | undefined ? never : T
function make(a: number, b: string): { id: number } { return { id: a } }
type R = ReturnType<typeof make> // { id: number }(依赖 infer,见第十四章)
type Dict = Record<'home' | 'about', string> // { home: string; about: string }
type MyRecord<K extends keyof any, V> = { [P in K]: V } // 手写:映射源是键联合
| 内置 | 作用 | 手写原理 |
|---|---|---|
| Partial / Required | 加 / 去可选 | { [K in keyof T]? / -?: T[K] } |
| Readonly | 加只读 | { readonly [K in keyof T]: T[K] } |
| Pick | 挑键 | { [P in K]: T[P] } |
| Omit | 去键 | Pick<T, Exclude<keyof T, K>> |
| Exclude / Extract | 联合筛选 | T extends U ? never / T : … |
| NonNullable | 去 null/undefined | T extends null|undefined ? never : T |
| ReturnType / Parameters | 拆函数 | T extends (…) => infer R ? R : never |
| Awaited | 剥 Promise | 递归 infer + PromiseLike<infer U> |
| Record | 键→值字典 | { [P in K]: V } |