TypeScript 工具类型全攻略:从入门到精通
TypeScript 工具类型全攻略:从入门到精通

一、前言:TypeScript 的瑞士军刀
TypeScript 的核心价值在于其强大的类型系统,它能在代码运行前捕获错误,提升代码的健壮性和可维护性。而 TypeScript 内置的工具类型(Utility Types),就像一把把精巧的瑞士军刀,让你能够以最少的代码,实现复杂的类型转换和操作。
本文将带你全面掌握这些必备的工具类型,不仅告诉你它们是什么、怎么用,更重要的是解释为什么这么用,以及在实际项目中如何组合运用它们来解决真实问题。
二、核心工具类型速查表
在深入讲解前,先奉上一张速查表,让你对所有工具类型有个整体印象。
| 分类 | 工具类型 | 作用 |
|---|---|---|
| 基础修饰 | Partial<T> | 将所有属性变为可选 |
Required<T> | 将所有属性变为必填 | |
Readonly<T> | 将所有属性变为只读 | |
| 结构挑选 | Pick<T, K> | 从 T 中挑选指定属性 K |
Omit<T, K> | 从 T 中剔除指定属性 K | |
Record<K, T> | 构建一个键为 K、值为 T 的对象类型 | |
| 类型过滤 | Exclude<T, U> | 从 T 中排除可以赋值给 U 的类型 |
Extract<T, U> | 从 T 中提取可以赋值给 U 的类型 | |
NonNullable<T> | 从 T 中排除 null 和 undefined | |
| 函数相关 | ReturnType<T> | 提取函数 T 的返回值类型 |
Parameters<T> | 提取函数 T 的参数类型元组 | |
ConstructorParameters<T> | 提取构造函数 T 的参数类型元组 | |
InstanceType<T> | 提取构造函数 T 的实例类型 | |
ThisParameterType<T> | 提取函数 T 的 this 参数类型 | |
OmitThisParameter<T> | 移除函数 T 的 this 参数限制 |
三、详细解析与实战场景
1. 基础修饰类:改变属性的“状态”
这类工具类型用于批量修改一个类型定义中所有属性的修饰符。
Partial<T>:让一切皆可选
Partial<T> 将类型 T 的所有属性都变为可选(Optional)。
类型定义:
type Partial<T> = { [P in keyof T]?: T[P]; };
代码示例:
interface Product {
id: string;
name: string;
price: number;
description: string;
}
// 使用 Partial 后,所有属性都变成了可选
type PartialProduct = Partial<Product>;
/*
{
id?: string | undefined;
name?: string | undefined;
price?: number | undefined;
description?: string | undefined;
}
*/
// 适用场景:更新操作
function updateProduct(productId: string, changes: Partial<Product>) {
// changes 对象可以只包含需要更新的字段
console.log(`Updating product ${productId} with`, changes);
}
updateProduct('p1', { price: 99.9, description: 'Updated desc' }); // ✅ 正确
updateProduct('p1', { name: 'New Name' }); // ✅ 正确
updateProduct('p1', {}); // ✅ 正确,不更新任何字段
Required<T>:让一切皆必填
Required<T> 与 Partial 相反,它将类型 T 的所有可选属性变为必填(Required)。
类型定义:
type Required<T> = { [P in keyof T]-?: T[P]; };
注意
-?符号,它的作用是移除属性的?可选修饰符。
代码示例:
interface Config {
endpoint?: string;
timeout?: number;
retries?: number;
}
// 使用 Required 后,所有属性都变成了必填
type StrictConfig = Required<Config>;
/*
{
endpoint: string;
timeout: number;
retries: number;
}
*/
// 适用场景:确保配置对象的完整性
function createClient(config: StrictConfig) {
// 在这里可以安全地使用 config.endpoint,无需检查 undefined
console.log(`Connecting to ${config.endpoint} with timeout ${config.timeout}`);
}
createClient({ endpoint: 'https://api.example.com', timeout: 5000, retries: 3 }); // ✅ 正确
// createClient({ endpoint: 'https://api.example.com' }); // ❌ 错误! Property 'timeout' is missing in type '{ endpoint: string; }' but required in type 'StrictConfig'.
Readonly<T>:让一切不可变
Readonly<T> 将类型 T 的所有属性都变为只读(Readonly)。
类型定义:
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
代码示例:
interface UserProfile {
username: string;
age: number;
}
// 使用 Readonly 后,所有属性都变成了只读
type ImmutableProfile = Readonly<UserProfile>;
/*
{
readonly username: string;
readonly age: number;
}
*/
const user: ImmutableProfile = { username: 'Alice', age: 30 };
// user.age = 31; // ❌ 错误! Cannot assign to 'age' because it is a read-only property.
// 适用场景:React Props、Redux State 等不希望被意外修改的数据
function DisplayUser(props: Readonly<UserProfile>) {
// props.username = 'Bob'; // ❌ 错误! 在 React 中,Props 是只读的
return `Hello, ${props.username}`;
}
2. 结构挑选类:重塑对象的“形状”
这类工具类型用于从一个类型中挑选或剔除属性,从而生成一个新的类型。
Pick<T, K>:精挑细选
Pick<T, K> 从类型 T 中,挑选出指定的属性 K 来构建一个新类型。
类型定义:
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
代码示例:
interface Order {
id: string;
customerId: string;
items: string[];
totalAmount: number;
status: 'pending' | 'paid' | 'shipped';
internalNotes?: string;
}
// 从 Order 中只挑选 id, items, totalAmount 三个属性
type OrderSummary = Pick<Order, 'id' | 'items' | 'totalAmount'>;
/*
{
id: string;
items: string[];
totalAmount: number;
}
*/
// 适用场景:API 响应、数据展示等只需要部分字段的场景
function getOrderSummary(order: Order): OrderSummary {
const { id, items, totalAmount } = order;
return { id, items, totalAmount };
}
Omit<T, K>:剔除无用
Omit<T, K> 与 Pick 相反,它从类型 T 中剔除指定的属性 K,然后用剩下的属性构建一个新类型。
类型定义:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
可以看到,
Omit是基于Pick和Exclude实现的,非常精妙。
代码示例:
interface User {
id: string;
email: string;
passwordHash: string; // 敏感信息
displayName: string;
}
// 剔除 passwordHash 属性,用于安全地对外展示用户信息
type PublicUserProfile = Omit<User, 'passwordHash'>;
/*
{
id: string;
email: string;
displayName: string;
}
*/
// 适用场景:隐藏敏感数据,构建 DTO (Data Transfer Object)
function toPublicProfile(user: User): PublicUserProfile {
const { passwordHash, ...publicInfo } = user;
return publicInfo;
}
Record<K, T>:批量生成
Record<K, T> 用于创建一个对象类型,其中键(Key)的类型是 K,值(Value)的类型是 T。
类型定义:
type Record<K extends keyof any, T> = { [P in K]: T; };
代码示例:
type Status = 'idle' | 'loading' | 'succeeded' | 'failed';
// 创建一个记录每种状态对应图标的对象类型
type StatusIcons = Record<Status, string>;
const icons: StatusIcons = {
idle: '⏹️',
loading: '⏳',
succeeded: '✅',
failed: '❌',
};
// 适用场景:状态映射、配置对象、字典等
type Page = 'home' | 'about' | 'contact';
const pageTitles: Record<Page, string> = {
home: '首页',
about: '关于我们',
contact: '联系方式',
};
3. 类型过滤类:精炼联合类型
这类工具类型用于对联合类型(Union Types)进行过滤和筛选。
Exclude<T, U>:排除异己
Exclude<T, U> 从联合类型 T 中,排除掉所有可以赋值给 U 的类型。
类型定义:
type Exclude<T, U> = T extends U ? never : T;
代码示例:
type AllEvents = 'click' | 'mouseover' | 'keydown' | 'keyup' | 'scroll';
type MouseEvents = 'click' | 'mouseover';
// 从 AllEvents 中排除 MouseEvents
type KeyboardAndScrollEvents = Exclude<AllEvents, MouseEvents>; // 'keydown' | 'keyup' | 'scroll'
// 适用场景:从一个大的集合中排除不需要的子集
function handleNonMouseEvent(event: KeyboardAndScrollEvents) {
console.log('Handling non-mouse event:', event);
}
Extract<T, U>:提取共有
Extract<T, U> 与 Exclude 相反,它从联合类型 T 中,提取出所有可以赋值给 U 的类型。
类型定义:
type Extract<T, U> = T extends U ? T : never;
代码示例:
type AllEvents = 'click' | 'mouseover' | 'keydown' | 'keyup' | 'scroll';
type TrackedEvents = 'click' | 'scroll' | 'focus';
// 提取 AllEvents 和 TrackedEvents 的交集
type TrackedAndSupportedEvents = Extract<AllEvents, TrackedEvents>; // 'click' | 'scroll'
// 适用场景:获取两个集合的交集,确定共同支持的类型
NonNullable<T>:排除空值
NonNullable<T> 从类型 T 中排除 null 和 undefined。
类型定义:
type NonNullable<T> = T extends null | undefined ? never : T;
代码示例:
type MaybeString = string | null | undefined;
// 排除 null 和 undefined
type DefinitelyString = NonNullable<MaybeString>; // string
// 适用场景:确保变量在使用前不为空
function logValue(value: DefinitelyString) {
// 在这里,value 一定是一个 string,可以安全地调用 string 方法
console.log(value.toUpperCase());
}
const input: MaybeString = 'hello';
if (input) {
logValue(input); // ✅ 在 if 判断后,TypeScript 知道 input 不为 null/undefined
}
4. 函数相关类:深入函数的“内部”
这类工具类型用于提取函数的参数、返回值、this 上下文等信息。
ReturnType<T>:捕获返回值
ReturnType<T> 提取函数类型 T 的返回值类型。
类型定义:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
infer R是 TypeScript 的类型推断关键字,它告诉 TS 去推断函数返回值的类型,并将其赋给R。
代码示例:
function getUserData() {
return {
id: '123',
name: 'Bob',
isPremium: false,
};
}
// 提取 getUserData 函数的返回值类型
type UserData = ReturnType<typeof getUserData>;
/*
{
id: string;
name: string;
isPremium: boolean;
}
*/
// 适用场景:当函数返回一个复杂对象时,可以自动获取其类型,避免重复定义接口
async function fetchFromAPI(endpoint: string) {
const response = await fetch(endpoint);
return response.json();
}
type ApiResponse = ReturnType<typeof fetchFromAPI>; // Promise<any>
// 在更复杂的场景下,可以结合泛型和类型守卫来获取更精确的类型
Parameters<T>:解构参数
Parameters<T> 提取函数类型 T 的参数类型,并以元组(Tuple)的形式返回。
类型定义:
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
代码示例:
function greet(name: string, greeting?: string) {
return `${greeting || 'Hello'}, ${name}!`;
}
// 提取 greet 函数的参数类型
type GreetParams = Parameters<typeof greet>; // [name: string, greeting?: string | undefined]
// 适用场景:创建一个可以接收与另一个函数相同参数的函数
function logAndCall(fn: typeof greet, ...args: GreetParams) {
console.log('Calling function with args:', args);
return fn(...args);
}
logAndCall(greet, 'Alice', 'Hi'); // ✅ 正确
ConstructorParameters<T>:构造函数的参数
ConstructorParameters<T> 提取构造函数类型 T 的参数类型元组。
类型定义:
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
代码示例:
class Point {
constructor(public x: number, public y: number) {}
}
// 提取 Point 构造函数的参数类型
type PointCtorParams = ConstructorParameters<typeof Point>; // [x: number, y: number]
// 适用场景:工厂模式,动态创建类的实例
function createInstance<T extends abstract new (...args: any) => any>(ctor: T, ...args: ConstructorParameters<T>): InstanceType<T> {
return new ctor(...args);
}
const p1 = createInstance(Point, 10, 20); // p1 类型是 Point
InstanceType<T>:构造函数的实例
InstanceType<T> 提取构造函数类型 T 所创建的实例的类型。
类型定义:
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
代码示例:
class UserService {
getUser(id: string) { /* ... */ }
}
// 提取 UserService 构造函数的实例类型
type UserServiceInstance = InstanceType<typeof UserService>; // UserService
// 适用场景:在依赖注入或 IoC 容器中,根据构造函数获取实例类型
let service: UserServiceInstance;
// service = new UserService(); // 正确
四、组合运用:解决复杂问题
真正的高手,懂得如何将这些基础工具类型组合起来,解决更复杂的类型挑战。
场景 1:优化 Redux 的 action 和 payload 类型
假设我们有一个 Redux store,我们想为 dispatch 函数创建一个类型安全的包装器。
// 1. 定义 action creators
const setUser = (user: { id: string; name: string }) => ({ type: 'user/set', payload: user });
const logout = () => ({ type: 'user/logout' });
const updateProfile = (changes: Partial<{ name: string; age: number }>) => ({ type: 'profile/update', payload: changes });
// 2. 获取所有 action creator 的返回值类型(即 action 的类型)
type AppActions = ReturnType<typeof setUser> | ReturnType<typeof logout> | ReturnType<typeof updateProfile>;
/*
| { type: 'user/set'; payload: { id: string; name: string; }; }
| { type: 'user/logout'; }
| { type: 'profile/update'; payload: Partial<...>; }
*/
// 3. 创建一个类型安全的 dispatch 函数
type ActionType = AppActions['type']; // 'user/set' | 'user/logout' | 'profile/update'
// 4. 为每个 action 类型创建 payload 类型的映射
type ActionPayloadMap = {
[A in AppActions as A['type']]: A extends { payload: infer P } ? P : undefined;
};
/*
{
'user/set': { id: string; name: string; };
'user/logout': undefined;
'profile/update': Partial<{ name: string; age: number; }>;
}
*/
// 5. 定义类型安全的 dispatch 函数
function safeDispatch<T extends ActionType>(type: T, payload: ActionPayloadMap[T]) {
// 在这里,payload 的类型会根据传入的 type 自动推断
console.log('Dispatching:', { type, payload });
}
// 6. 使用
safeDispatch('user/set', { id: '1', name: 'Alice' }); // ✅ 正确
safeDispatch('user/logout', undefined); // ✅ 正确
safeDispatch('profile/update', { age: 30 }); // ✅ 正确
// safeDispatch('profile/update', { invalidProp: 'test' }); // ❌ 错误! 类型检查会捕获
在这个例子中,我们组合使用了 ReturnType, infer, 和映射类型,创建了一个高度类型安全的 dispatch 函数,大大提升了代码的可维护性。
场景 2:创建只读且无敏感信息的实体视图
interface UserEntity {
id: string;
email: string;
passwordHash: string;
createdAt: Date;
updatedAt: Date;
}
// 组合 Omit 和 Readonly
type SafeUserView = Readonly<Omit<UserEntity, 'passwordHash'>>;
/*
{
readonly id: string;
readonly email: string;
readonly createdAt: Date;
readonly updatedAt: Date;
}
*/
// 适用场景:从数据库获取实体后,准备返回给前端时使用
function toSafeUserView(user: UserEntity): SafeUserView {
const { passwordHash, ...safeInfo } = user;
return safeInfo; // 返回的对象是只读的,且不含敏感信息
}
五、总结与下一步
总结
- 基础修饰 (
Partial,Required,Readonly):用于批量修改属性的“状态”。 - 结构挑选 (
Pick,Omit,Record):用于重塑对象的“形状”。 - 类型过滤 (
Exclude,Extract,NonNullable):用于精炼联合类型。 - 函数相关 (
ReturnType,Parameters,ConstructorParameters,InstanceType):用于深入函数的“内部”。
这些工具类型是 TypeScript 类型编程的基石。熟练掌握它们,能让你告别大量的重复类型声明,写出更简洁、更健壮、更具表达力的代码。
下一步
- 动手实践:将这些工具类型应用到你的日常开发中,解决遇到的实际问题。
- 深入理解:尝试自己实现这些工具类型(参考上面的“类型定义”部分),这将极大加深你对 TypeScript 类型系统(尤其是条件类型和映射类型)的理解。
- 探索进阶:学习更高级的类型技巧,如条件类型、映射类型修饰符(
+?,-?,readonly)、递归类型和模板字面量类型,它们能让你构建出更加强大和灵活的类型。
希望这份详尽的攻略能帮助你打开 TypeScript 类型世界的大门,让你写出更优雅、更自信的代码!
更多推荐


所有评论(0)