三块面板:TS 源码(含手写映射类型)/ 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。工具类型本质都是映射类型「遍历键联合逐个生成属性」的组装。

一、Partial / Pick:内置版 vs 手写版

TS 源码

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] }   // 手写:映射源是要保留的键
pick.ts:7:18 - error TS2344: Type '"token"' does not satisfy the constraint 'keyof User'. 7 Pick<User, 'token'> // 'token' 不是 User 的键 ~~~~~~
为什么报:Pick 的第二个类型参数受 extends keyof T 约束,只能填真实存在的键。'token' 不在 User 上,越界即报错——这正是 Pick 的安全价值。

等价 JS:Partial/Pick 都只在类型层,运行时无物

类型擦除后,下面这段等价 JS 演示的只是「运行时对对象的操作」,跟 Partial/Pick 无关——这恰好印证工具类型不产出任何代码。

[迷你控制台]

二、Exclude / Extract / NonNullable:作用在联合上的筛选器

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

三、ReturnType / Record

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/undefinedT extends null|undefined ? never : T
ReturnType / Parameters拆函数T extends (…) => infer R ? R : never
Awaited剥 Promise递归 infer + PromiseLike<infer U>
Record键→值字典{ [P in K]: V }
看清了吗:每个工具类型都是第二章–第十四章零件(keyof、索引访问、映射类型、条件类型、infer)的组装。读懂这一层,工具类型就从“背 API”变成“懂原理”。