当前位置: 首页 > ds >正文

实现 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 的所有属性变为可选。

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

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 相反,移除可选修饰符 ?

type MyRequired<T> = {[K in keyof T]-?: T[K];
};// 示例
interface Opt { a?: number }
type R = MyRequired<Opt>; // { a: number }

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


3. Readonly<T>

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

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

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),并把对应属性取出。

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 出剩余的键。

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

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)的一种应用:

// 分布式条件类型:当 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 相反:

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>

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

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


10. ReturnType<T>

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

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

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

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 提取构造参数。

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

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

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 的函数类型。

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 的包裹类型等)。

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


http://www.xdnf.cn/news/20024.html

相关文章:

  • C语言中的运算符
  • 自动化运维-ansible中的条件判断
  • 前端框架(Vue/React):界面更新的运行链路
  • mysy2使用
  • CC攻击的主要来源
  • 鸿蒙Next图形绘制指南:从基础几何图形到复杂UI设计
  • vue3 vite 自适应方案
  • Java+AI开发实战与知识点归纳系列:Spring流式输出实战——LangChain4j与Ollama集成
  • 2025 大数据时代值得考的证书排名前八​
  • TypeScript与JavaScript:从动态少年到稳重青年的成长之路
  • “企业版维基百科”Confluence
  • STM32 - Embedded IDE - GCC - 如何在工程中定义一段 NoInit RAM 内存
  • 爬取m3u8视频完整教程
  • JavaWeb项目在服务器部署
  • 数据结构之----线性表其一---顺序表
  • 弱电太累,职业发展遇瓶颈?那一定不要错过这个技能!
  • 单片机(89C51)---基础知识
  • 阅兵时刻,耐达讯自动化RS485 转 Profinet 网关助力矿山冶金连接迈向辉煌
  • 【大数据技术实战】Flink+DS+Dinky 自动化构建数仓平台
  • 嵌入式 Linux 启动流程详解 (以 ARM + U-Boot 为例)
  • 【ShiMetaPi M4-R1】上手:RK3568B2|开源鸿蒙(OpenHarmony) 应用开发快速上手
  • Vue+Echarts饼图深度美化指南:打造卓越数据可视化体验
  • 深入理解 Java 集合框架:底层原理与实战应用
  • 0元部署私有n8n,免费的2CPU+16GB服务器,解锁无限制的工作流体验
  • ruoyi vue element 实现点击、返回首页收起已经展开的菜单栏
  • SpringBoot 整合 Kafka 的实战指南
  • 《用 Django 构建博客应用:从模型设计到文章管理的全流程实战》
  • 2025年11月GIS应用技术测评考试(附考试资料分享)
  • 【开题答辩全过程】以 校园安全管理系统设计与实现为例,包含答辩的问题和答案
  • Django 命令大全:从入门到精通,开发者必备指南