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 中排除 nullundefined
函数相关ReturnType<T>提取函数 T返回值类型
Parameters<T>提取函数 T参数类型元组
ConstructorParameters<T>提取构造函数 T参数类型元组
InstanceType<T>提取构造函数 T实例类型
ThisParameterType<T>提取函数 Tthis 参数类型
OmitThisParameter<T>移除函数 Tthis 参数限制

三、详细解析与实战场景

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 是基于 PickExclude 实现的,非常精妙。

代码示例

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 中排除 nullundefined

类型定义

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 的 actionpayload 类型

假设我们有一个 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 类型编程的基石。熟练掌握它们,能让你告别大量的重复类型声明,写出更简洁、更健壮、更具表达力的代码。

下一步

  1. 动手实践:将这些工具类型应用到你的日常开发中,解决遇到的实际问题。
  2. 深入理解:尝试自己实现这些工具类型(参考上面的“类型定义”部分),这将极大加深你对 TypeScript 类型系统(尤其是条件类型和映射类型)的理解。
  3. 探索进阶:学习更高级的类型技巧,如条件类型映射类型修饰符+?, -?, readonly)、递归类型模板字面量类型,它们能让你构建出更加强大和灵活的类型。

希望这份详尽的攻略能帮助你打开 TypeScript 类型世界的大门,让你写出更优雅、更自信的代码!


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐