目标读者:已经熟悉 TypeScript 基础语法、泛型、条件类型的同学。本文按常见工具类型的分类与顺序实现并解释
Partial
、Required
、Readonly
、Pick
、Omit
、Record
、Exclude
、Extract
、NonNullable
、ReturnType
、Parameters
、ConstructorParameters
、InstanceType
、ThisParameterType
、OmitThisParameter
。
目录(与上一篇顺序一致)
- Partial
- Required
- Readonly
- Pick
- Omit
- Record
- Exclude
- Extract
- NonNullable
- ReturnType
- Parameters
- ConstructorParameters
- InstanceType
- ThisParameterType
- 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 的包裹类型等)。
掌握这些技巧之后,你可以读懂并自己实现更复杂的工具类型,写出更类型安全、可复用的代码库。