内置工具类型源码
所有内置工具类型都在 20 行以内。看懂它们的实现,等于掌握了 type-challenges 里 easy + medium 的一大半。
下面是 TypeScript 官方 lib.es5.d.ts 里的真实定义(略有简化)。
对象操作
Partial<T>映射 + 修饰符把所有属性变成可选。
ts
type Partial<T> = { [P in keyof T]?: T[P] }Required<T>移除修饰符把所有属性变成必填。
ts
type Required<T> = { [P in keyof T]-?: T[P] }Readonly<T>映射ts
type Readonly<T> = { readonly [P in keyof T]: T[P] }Pick<T, K>约束 + 映射ts
type Pick<T, K extends keyof T> = { [P in K]: T[P] }K extends keyof T 保证只能挑存在的键。
Omit<T, K>Pick + Excludets
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>注意它是浅层的,嵌套对象里的键不会被剔除。
ts
type DeepOmit<T, K extends string> = T extends (infer U)[]
? DeepOmit<U, K>[]
: T extends object
? { [P in Exclude<keyof T, K>]: DeepOmit<T[P], K> }
: TRecord<K, T>映射ts
type Record<K extends keyof any, T> = { [P in K]: T }联合操作
Exclude<T, U>分发ts
type Exclude<T, U> = T extends U ? never : T原理:分发后不满足的变成 never,never 在联合里消失。
Extract<T, U>分发ts
type Extract<T, U> = T extends U ? T : neverNonNullable<T>TS 4.8 改过实现ts
// 4.8 之前
type Old<T> = T extends null | undefined ? never : T
// 4.8 之后
type NonNullable<T> = T & {}改成 T & {} 的原因:旧实现会分发,把 NonNullable<string | null> 变成 string 是对的,但对 any 的处理有 bug(any 会被分发成两个分支)。T & {} 直接利用"交叉 {} 排除 null/undefined"的结构性质,更快也更准。
函数操作
Parameters<T>inferts
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : neverReturnType<T>inferts
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any注意兜底是 any 不是 never。
ConstructorParameters / InstanceTypeabstract newts
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : anyabstract new 是关键——加了 abstract 才能同时匹配普通类和抽象类。
ThisParameterType / OmitThisParameterthis 参数ts
type ThisParameterType<T> =
T extends (this: infer U, ...args: never) => any ? U : unknown
type OmitThisParameter<T> =
unknown extends ThisParameterType<T>
? T
: T extends (...args: infer A) => infer R ? (...args: A) => R : T异步
Awaited<T>TS 4.5 · 递归ts
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F): any }
? F extends (value: infer V, ...args: any) => any
? Awaited<V>
: never
: T这段源码本身就很值得背:
- 先排除
null | undefined - 用
object & { then(...) }匹配 thenable(不只是Promise,任何有then的对象都行) - 从
onfulfilled回调里infer出值的类型V - 对
V递归调用自己
ts
type A = Awaited<Promise<Promise<number>>>
type B = Awaited<Promise<string> | number>
NoInfer<T>TS 5.4 · intrinsic编译器内部实现,没有 TS 层源码。作用是阻止类型参数参与推断。
ts
declare function f1<T>(items: T[], init: T): T
declare function f2<T>(items: T[], init: NoInfer<T>): T
const a = f1(['x', 'y'], 'z')
type A = typeof a
f1 会把 T 推成 'x' | 'y' | 'z';f2 只从 items 推断,init 只做校验。
字符串
Uppercase / Lowercase / Capitalize / Uncapitalizeintrinsic四个都是编译器内部实现,无法用 TS 表达。
ts
type A = Capitalize<'abc'>
手写高频补充
官方没有但项目里几乎必用:
Mutable<T>常用ts
type Mutable<T> = { -readonly [P in keyof T]: T[P] }Prettify<T>调试必备ts
type Prettify<T> = { [K in keyof T]: T[K] } & {}把交叉类型拍平成可读的对象字面量。
ts
type Prettify<T> = { [K in keyof T]: T[K] } & {}
type P = Prettify<{ a: string } & { b: number }>
DeepPartial<T>常用ts
type DeepPartial<T> = T extends (infer U)[]
? DeepPartial<U>[]
: T extends Function
? T
: T extends object
? { [P in keyof T]?: DeepPartial<T[P]> }
: T顺序很关键:数组 → 函数 → 对象,因为前两者都是 object 的子类型。
UnionToIntersection<U>经典题ts
type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never利用函数参数位置的逆变把联合转成交叉。
ts
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never
type R = UnionToIntersection<{ a: 1 } | { b: 2 }>