实现 TypeScript 内置工具类型(源码解析与实现)

目标读者:已经熟悉 TypeScript 基础语法、泛型、条件类型的同学。本文按常见工具类型的分类与顺序实现并解释 PartialRequiredReadonlyPickOmitRecordExcludeExtractNonNullableReturnTypeParametersConstructorParametersInstanceTypeThisParameterTypeOmitThisParameter


目录(与上一篇顺序一致)

  1. Partial
  2. Required
  3. Readonly
  4. Pick
  5. Omit
  6. Record
  7. Exclude
  8. Extract
  9. NonNullable
  10. ReturnType
  11. Parameters
  12. ConstructorParameters
  13. InstanceType
  14. ThisParameterType
  15. OmitThisParameter

注意:以下实现主要用于教学与阅读(与 TypeScript 官方实现甚为相近),可帮助理解背后的类型技巧(映射类型、条件类型、inferkeyof 等)。


1. Partial<T>

用途回顾 :把类型 T 的所有属性变为可选。

实现思路 :使用映射类型把每个属性的修饰符 ? 添加上。

ts 复制代码
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// 示例
interface User { id: number; name: string }
type PUser = MyPartial<User>; // { id?: number; name?: string }

要点[K in keyof T] 遍历 T 的所有键,?: 表示可选属性。


2. Required<T>

用途回顾 :把 T 的所有属性变为必选。

实现思路 :与 Partial 相反,移除可选修饰符 ?

ts 复制代码
type MyRequired<T> = {
  [K in keyof T]-?: T[K];
};

// 示例
interface Opt { a?: number }
type R = MyRequired<Opt>; // { a: number }

要点-? 是映射类型的语法,用来移除可选标记。


3. Readonly<T>

用途回顾 :把 T 的所有属性变为只读。

实现思路 :使用映射类型并加上 readonly 修饰符。

ts 复制代码
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

// 示例
type R = MyReadonly<{ x: number }>; // { readonly x: number }

要点readonly?-? 一样都是映射类型可用的修饰符。


4. Pick<T, K>

用途回顾 :从 T 中挑选一部分属性 KK 是键的联合类型)。

实现思路 :遍历 Kextends keyof T),并把对应属性取出。

ts 复制代码
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

// 示例
interface User { id: number; name: string; age: number }
type Preview = MyPick<User, 'id' | 'name'>; // { id: number; name: string }

要点K extends keyof T 约束保证 K 只包含 T 的键。


5. Omit<T, K>

用途回顾 :从 T 中排除某些属性 K

实现思路Omit<T, K> 通常等价于从 T 的键中 Exclude<keyof T, K>,然后 Pick 出剩余的键。

ts 复制代码
type MyOmit<T, K extends keyof any> = MyPick<T, Exclude<keyof T, K>>;

// 或者直接写成:
// type MyOmit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P] }

// 示例
interface User { id: number; name: string; password: string }
type Safe = MyOmit<User, 'password'>; // { id: number; name: string }

要点K extends keyof any(或 keyof T)使得 K 可以是字符串字面量等;Exclude 在后面会解释。


6. Record<K, T>

用途回顾 :构造一个以联合类型 K 为键,值为 T 的对象类型。

实现思路 :映射类型直接遍历 K

ts 复制代码
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

// 示例
type Roles = 'admin' | 'user';
type RoleCount = MyRecord<Roles, number>; // { admin: number; user: number }

要点keyof any 表示允许任意作为 object key 的类型(string | number | symbol)。


7. Exclude<T, U>

用途回顾 :从联合类型 T 中排除能赋值给 U 的成员。

实现思路Exclude 是分布式条件类型(conditional type)的一种应用:

ts 复制代码
// 分布式条件类型:当 T 是联合类型时,条件类型会对联合的每个成员逐一计算
// 例如: T = A | B, 则 T extends U ? X : Y 会计算 A extends U ? X : Y 以及 B extends U ? X : Y,然后把结果联合在一起

type MyExclude<T, U> = T extends U ? never : T;

// 示例
type E = MyExclude<'a' | 'b' | 'c', 'a' | 'f'>; // 'b' | 'c'

要点Exclude 利用了条件类型的"分布式"特性:当 T 是联合类型时,T extends ... 会分解处理每个成员。


8. Extract<T, U>

用途回顾 :从 T 中提取可赋值给 U 的成员(交集)。

实现思路 :和 Exclude 相反:

ts 复制代码
type MyExtract<T, U> = T extends U ? T : never;

// 示例
type X = MyExtract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'

要点:同样利用了条件类型的分布式行为。


9. NonNullable<T>

用途回顾 :移除 nullundefined

实现思路 :等价于 Exclude<T, null | undefined>

ts 复制代码
type MyNonNullable<T> = MyExclude<T, null | undefined>;

// 示例
type N = MyNonNullable<string | null | undefined>; // string

要点:这是组合前面工具类型的好例子。


10. ReturnType<T>

用途回顾 :获取函数类型 T 的返回值类型。

实现思路 :使用 infer 在条件类型中提取返回类型。

ts 复制代码
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

// 示例
function foo() { return { x: 1 } }
type FooRet = MyReturnType<typeof foo>; // { x: number }

要点infer R 用来声明并捕获返回类型。


11. Parameters<T>

用途回顾 :获取函数类型 T 的参数类型元组。

实现思路 :同样用 infer 提取参数元组 P

ts 复制代码
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

function greet(a: string, b: number) {}
type G = MyParameters<typeof greet>; // [string, number]

要点 :通过 infer P 捕获参数列表的类型元组。


12. ConstructorParameters<T>

用途回顾:获取构造函数类型(类/构造签名)的参数元组。

实现思路 :这里 Tnew (...args: any) => any 的构造签名;用 infer 提取构造参数。

ts 复制代码
type MyConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

class Person { constructor(name: string, age: number) {} }
type C = MyConstructorParameters<typeof Person>; // [string, number]

要点abstract new 是为了兼容普通类与抽象构造签名。


13. InstanceType<T>

用途回顾 :给定一个构造函数类型 T,返回其实例类型。

实现思路 :使用条件类型匹配 new (...args: any) => infer R,返回实例 R

ts 复制代码
type MyInstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

class Person { name = 'tom' }
type P = MyInstanceType<typeof Person>; // Person

要点InstanceType 常用于库设计或工厂模式中从类类型推导实例类型。


14. ThisParameterType<T>

用途回顾 :提取函数类型中的 this 参数类型(如果有)。

实现思路 :匹配 this: X 形式的函数签名并用 infer 捕获 X

ts 复制代码
type MyThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

function fn(this: { name: string }, a: number) { return this.name }
type ThisT = MyThisParameterType<typeof fn>; // { name: string }

要点 :如果函数没有 this 参数,官方实现会返回 unknown


15. OmitThisParameter<T>

用途回顾 :移除函数类型中的 this 参数,得到普通函数类型(用于 bind/call 时的 type convenience)。

实现思路 :如果函数包含 this 参数,将其转换为不含 this 的函数类型。

ts 复制代码
type MyOmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (this: any, ...args: infer P) => infer R ? (...args: P) => R : T;

// 示例
function say(this: { name: string }, n: number) { return this.name + n }
type FnNoThis = MyOmitThisParameter<typeof say>; // (n: number) => string

要点 :实现里先检查 ThisParameterType<T> 是否为 unknown(即函数没有显式 this),如果是就直接返回原类型 T;否则提取参数与返回值并重构为不带 this 的函数类型。


额外:官方实现 vs 教学实现差异

  • 官方的实现会有更多兼容性考量、any/unknown 微妙行为处理,以及对 TS 版本特性的更细致支持(例如 abstract newthis 分支的边界情况)。
  • 教学实现避免极端兼容性,为了可读性而做了简化,但核心思想一致。

小结

通过实现这些常用工具类型,你可以更清楚地理解:

  • 映射类型[K in keyof T])是如何构造新类型的;
  • 条件类型与其分布式特性如何在联合类型上逐项计算;
  • infer 如何在条件类型中提取内部类型(函数参数、返回值、Promise 的包裹类型等)。

掌握这些技巧之后,你可以读懂并自己实现更复杂的工具类型,写出更类型安全、可复用的代码库。


相关推荐
learning_tom3 小时前
HTML5 标题标签、段落、换行和水平线
前端·html·html5
IT_陈寒3 小时前
Python性能优化:这5个隐藏技巧让我的代码提速300%!
前端·人工智能·后端
Dolphin_海豚3 小时前
【译】Reading vuejs/core-vapor - 中卷
前端·掘金翻译计划·vapor
只与明月听3 小时前
前端缓存知多少
前端·面试·html
Dolphin_海豚3 小时前
【译】Vue.js 下一代实现指南 - 下卷
前端·掘金翻译计划·vapor
Apifox3 小时前
理解和掌握 Apifox 中的变量(临时、环境、模块、全局变量等)
前端·后端·测试
小白_ysf3 小时前
阿里云日志服务之WebTracking 小程序端 JavaScript SDK (阿里SDK埋点和原生uni.request请求冲突问题)
前端·微信小程序·uni-app·埋点·阿里云日志服务
你的电影很有趣3 小时前
lesson52:CSS进阶指南:雪碧图与边框技术的创新应用
前端·css
Jerry4 小时前
Compose 延迟布局
前端