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

目标读者:已经熟悉 TypeScript 基础语法、泛型、条件类型的同学。本文按常见工具类型的分类与顺序实现并解释 Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、NonNullable、ReturnType、Parameters、ConstructorParameters、InstanceType、ThisParameterType、OmitThisParameter。


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

  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 官方实现甚为相近),可帮助理解背后的类型技巧(映射类型、条件类型、infer、keyof 等)。


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 中挑选一部分属性 K(K 是键的联合类型)。

实现思路 :遍历 K(extends 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>

用途回顾 :移除 null 和 undefined。

实现思路 :等价于 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>

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

实现思路 :这里 T 是 new (...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 new、this 分支的边界情况)。
  • 教学实现避免极端兼容性,为了可读性而做了简化,但核心思想一致。

小结

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

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

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


相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万4 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋4 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁4 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95274 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大4 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师4 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学4 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端