Skip to content

内置工具类型

概述

TypeScript 提供了丰富的内置工具类型(Utility Types),这些工具类型基于映射类型和条件类型实现,可以帮助我们快速创建新的类型,而无需手动编写复杂的类型定义。工具类型是 TypeScript 类型系统的重要组成部分,掌握它们可以大大提高开发效率和代码质量。

提示

工具类型都是基于映射类型和条件类型实现的,理解这些基础概念有助于更好地使用工具类型。建议先学习映射类型条件类型

对象属性操作工具类型

Partial<T>

将对象类型的所有属性变为可选。

实现原理

typescript
type Partial<T> = {
  [P in keyof T]?: T[P];
};

使用示例

typescript
interface User {
  name: string;
  age: number;
  email: string;
}

// 所有属性变为可选
type PartialUser = Partial<User>;
// 类型:{ name?: string; age?: number; email?: string; }

// 使用场景:更新用户信息时,只需要提供部分字段
function updateUser(id: number, updates: Partial<User>) {
  // 可以只更新部分属性
}

updateUser(1, { name: "John" });  // ✅ 只更新 name
updateUser(1, { age: 30 });       // ✅ 只更新 age
updateUser(1, { name: "John", age: 30 });  // ✅ 更新多个属性

Required<T>

将对象类型的所有属性变为必需。

实现原理

typescript
type Required<T> = {
  [P in keyof T]-?: T[P];
};

使用示例

typescript
interface Config {
  host?: string;
  port?: number;
  timeout?: number;
}

// 所有属性变为必需
type RequiredConfig = Required<Config>;
// 类型:{ host: string; port: number; timeout: number; }

// 使用场景:确保配置对象包含所有必需的属性
function createConnection(config: RequiredConfig) {
  // config 的所有属性都是必需的
  console.log(config.host, config.port, config.timeout);
}

// ❌ 错误:缺少必需属性
// createConnection({ host: "localhost" });

// ✅ 正确:提供所有必需属性
createConnection({
  host: "localhost",
  port: 3000,
  timeout: 5000
});

Readonly<T>

将对象类型的所有属性变为只读。

实现原理

typescript
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

使用示例

typescript
interface User {
  name: string;
  age: number;
}

// 所有属性变为只读
type ReadonlyUser = Readonly<User>;
// 类型:{ readonly name: string; readonly age: number; }

// 使用场景:创建不可变的对象
const user: ReadonlyUser = {
  name: "John",
  age: 30
};

// ❌ 错误:无法修改只读属性
// user.name = "Jane";  // Error: Cannot assign to 'name' because it is a read-only property
// user.age = 31;       // Error: Cannot assign to 'age' because it is a read-only property

提示

Readonly 只影响第一层属性,对于嵌套对象,需要使用深度只读类型。可以参考递归类型章节。

属性选择工具类型

Pick<T, K>

从类型 T 中选择指定的属性 K

实现原理

typescript
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

使用示例

typescript
interface User {
  name: string;
  age: number;
  email: string;
  phone: string;
  address: string;
}

// 只选择 name 和 email 属性
type UserContact = Pick<User, 'name' | 'email'>;
// 类型:{ name: string; email: string; }

// 使用场景:API 响应只需要部分字段
function getUserContact(id: number): Pick<User, 'name' | 'email'> {
  return {
    name: "John",
    email: "john@example.com"
  };
}

// 选择多个属性
type UserBasic = Pick<User, 'name' | 'age'>;
// 类型:{ name: string; age: number; }

Omit<T, K>

从类型 T 中排除指定的属性 K

实现原理

typescript
type Omit<T, K extends keyof any> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

使用示例

typescript
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  password: string;
}

// 排除 id 和 password 属性
type PublicUser = Omit<User, 'id' | 'password'>;
// 类型:{ name: string; age: number; email: string; }

// 使用场景:创建用户时不需要 id(由数据库生成)
function createUser(userData: Omit<User, 'id'>) {
  // userData 不包含 id
  return {
    id: generateId(),
    ...userData
  };
}

// 排除单个属性
type UserWithoutPassword = Omit<User, 'password'>;
// 类型:{ id: number; name: string; age: number; email: string; }

提示

OmitPick 是互补的工具类型。Omit<T, K> 等价于 Pick<T, Exclude<keyof T, K>>

类型创建工具类型

Record<K, T>

创建一个对象类型,其键为 K,值为 T

实现原理

typescript
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

使用示例

typescript
// 创建键值对类型
type UserRoles = Record<string, boolean>;
// 类型:{ [key: string]: boolean; }

// 使用场景:用户权限映射
const permissions: Record<string, boolean> = {
  read: true,
  write: false,
  delete: false
};

// 使用字面量类型作为键
type Status = 'pending' | 'approved' | 'rejected';
type StatusConfig = Record<Status, { color: string; icon: string }>;
// 类型:{ pending: { color: string; icon: string }; approved: { color: string; icon: string }; rejected: { color: string; icon: string }; }

const statusConfig: StatusConfig = {
  pending: { color: 'yellow', icon: '⏳' },
  approved: { color: 'green', icon: '✅' },
  rejected: { color: 'red', icon: '❌' }
};

// 创建枚举映射
enum UserRole {
  Admin = 'admin',
  User = 'user',
  Guest = 'guest'
}

type RolePermissions = Record<UserRole, string[]>;
// 类型:{ admin: string[]; user: string[]; guest: string[]; }

函数类型工具类型

Parameters<T>

提取函数类型的参数类型,返回一个元组类型。

实现原理

typescript
type Parameters<T extends (...args: any) => any> = 
  T extends (...args: infer P) => any ? P : never;

使用示例

typescript
function createUser(name: string, age: number, email: string): void {
  // ...
}

// 提取函数参数类型
type CreateUserParams = Parameters<typeof createUser>;
// 类型:[string, number, string]

// 使用场景:复用函数参数类型
function validateUserInput(...args: Parameters<typeof createUser>) {
  const [name, age, email] = args;
  // 验证逻辑
}

// 提取异步函数参数
async function fetchUser(id: number, options: { cache?: boolean }): Promise<User> {
  // ...
}

type FetchUserParams = Parameters<typeof fetchUser>;
// 类型:[number, { cache?: boolean }]

ReturnType<T>

提取函数类型的返回类型。

实现原理

typescript
type ReturnType<T extends (...args: any) => any> = 
  T extends (...args: any) => infer R ? R : any;

使用示例

typescript
function getUser(id: number): User {
  return { id, name: "John", age: 30 };
}

// 提取函数返回类型
type UserType = ReturnType<typeof getUser>;
// 类型:User

// 使用场景:复用函数返回类型
async function getUserAsync(id: number): Promise<ReturnType<typeof getUser>> {
  // 返回类型与 getUser 相同
  return getUser(id);
}

// 提取 Promise 返回类型
async function fetchData(): Promise<{ data: string[] }> {
  return { data: [] };
}

type DataType = ReturnType<typeof fetchData>;
// 类型:Promise<{ data: string[] }>

提示

对于返回 Promise 的函数,ReturnType 会返回 Promise<T> 类型。如果需要提取 Promise 内部的类型,可以使用 Awaited 工具类型。

ConstructorParameters<T>

提取构造函数类型的参数类型。

使用示例

typescript
class User {
  constructor(
    public name: string,
    public age: number,
    public email: string
  ) {}
}

// 提取构造函数参数类型
type UserConstructorParams = ConstructorParameters<typeof User>;
// 类型:[string, number, string]

// 使用场景:创建工厂函数
function createUser(...args: ConstructorParameters<typeof User>): User {
  return new User(...args);
}

// 提取抽象类构造函数参数
abstract class BaseService {
  constructor(protected config: { apiKey: string }) {}
}

type BaseServiceParams = ConstructorParameters<typeof BaseService>;
// 类型:[{ apiKey: string }]

InstanceType<T>

提取构造函数类型的实例类型。

使用示例

typescript
class User {
  name: string;
  age: number;
  
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// 提取类实例类型
type UserInstance = InstanceType<typeof User>;
// 类型:User

// 使用场景:泛型工厂函数
function createInstance<T extends new (...args: any) => any>(
  constructor: T,
  ...args: ConstructorParameters<T>
): InstanceType<T> {
  return new constructor(...args);
}

const user = createInstance(User, "John", 30);
// user 类型:User

Promise 相关工具类型

Awaited<T>

提取 Promise 类型的内部类型(TypeScript 4.5+)。

使用示例

typescript
// 提取 Promise 内部类型
type StringPromise = Promise<string>;
type StringType = Awaited<StringPromise>;
// 类型:string

// 处理嵌套 Promise
type NestedPromise = Promise<Promise<number>>;
type NumberType = Awaited<NestedPromise>;
// 类型:number

// 使用场景:提取异步函数返回的实际类型
async function fetchUser(): Promise<User> {
  return { id: 1, name: "John" };
}

type UserType = Awaited<ReturnType<typeof fetchUser>>;
// 类型:User(而不是 Promise<User>)

// 处理联合类型中的 Promise
type MaybePromise = string | Promise<number>;
type Resolved = Awaited<MaybePromise>;
// 类型:string | number

提示

Awaited 是 TypeScript 4.5 引入的工具类型,用于替代之前常用的手动提取 Promise 类型的方式。它能够正确处理嵌套的 Promise 类型。

字符串操作工具类型

TypeScript 4.1+ 引入了字符串字面量类型的操作工具类型,用于操作字符串字面量类型。

Uppercase<S>

将字符串字面量类型转换为大写。

typescript
type Greeting = "hello";
type UpperGreeting = Uppercase<Greeting>;
// 类型:"HELLO"

// 使用场景:创建常量类型
type EventName = "click" | "hover" | "focus";
type EventHandlerName = `on${Uppercase<EventName>}`;
// 类型:"onCLICK" | "onHOVER" | "onFOCUS"

Lowercase<S>

将字符串字面量类型转换为小写。

typescript
type Status = "PENDING" | "APPROVED";
type LowerStatus = Lowercase<Status>;
// 类型:"pending" | "approved"

Capitalize<S>

将字符串字面量类型的首字母转换为大写。

typescript
type Name = "john" | "jane";
type CapitalizedName = Capitalize<Name>;
// 类型:"John" | "Jane"

// 使用场景:创建属性名
type PropName = "name" | "age";
type GetterName = `get${Capitalize<PropName>}`;
// 类型:"getName" | "getAge"

Uncapitalize<S>

将字符串字面量类型的首字母转换为小写。

typescript
type Method = "Get" | "Post";
type LowerMethod = Uncapitalize<Method>;
// 类型:"get" | "post"

提示

这些字符串操作工具类型主要用于模板字面量类型中,可以创建更灵活的类型定义。更多内容请参考模板字面量类型

类型操作工具类型

Exclude<T, U>

从类型 T 中排除可以赋值给 U 的类型。

实现原理

typescript
type Exclude<T, U> = T extends U ? never : T;

使用示例

typescript
type AllColors = "red" | "green" | "blue" | "yellow";
type PrimaryColors = "red" | "green" | "blue";

// 排除 PrimaryColors,得到其他颜色
type OtherColors = Exclude<AllColors, PrimaryColors>;
// 类型:"yellow"

// 使用场景:从联合类型中排除特定类型
type NonNullable<T> = Exclude<T, null | undefined>;

type MaybeString = string | null | undefined;
type StringType = NonNullable<MaybeString>;
// 类型:string

Extract<T, U>

从类型 T 中提取可以赋值给 U 的类型。

实现原理

typescript
type Extract<T, U> = T extends U ? T : never;

使用示例

typescript
type AllTypes = string | number | boolean | null;
type PrimitiveTypes = string | number | boolean;

// 提取原始类型,排除 null
type Primitives = Extract<AllTypes, PrimitiveTypes>;
// 类型:string | number | boolean

// 使用场景:从联合类型中提取特定类型
type FunctionTypes = Extract<
  string | number | (() => void) | boolean,
  Function
>;
// 类型:() => void

NonNullable<T>

从类型 T 中排除 nullundefined

实现原理

typescript
type NonNullable<T> = T extends null | undefined ? never : T;

使用示例

typescript
type MaybeString = string | null | undefined;
type StringType = NonNullable<MaybeString>;
// 类型:string

// 使用场景:确保值不为空
function processValue<T>(value: NonNullable<T>): void {
  // value 不会是 null 或 undefined
  console.log(value);
}

// ❌ 错误:不能传入 null
// processValue<string>(null);

// ✅ 正确
processValue<string>("hello");

实际应用场景

场景 1:API 请求和响应类型

typescript
// 定义完整的用户类型
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// 创建用户时不需要 id 和 createdAt
type CreateUserRequest = Omit<User, 'id' | 'createdAt'>;
// 类型:{ name: string; email: string; password: string; }

// API 响应不包含敏感信息
type UserResponse = Omit<User, 'password'>;
// 类型:{ id: number; name: string; email: string; createdAt: Date; }

// 更新用户时所有字段都是可选的
type UpdateUserRequest = Partial<Omit<User, 'id' | 'createdAt'>>;
// 类型:{ name?: string; email?: string; password?: string; }

场景 2:表单类型处理

typescript
interface FormData {
  name: string;
  email: string;
  age: number;
  agreeToTerms: boolean;
}

// 表单提交时,所有字段都是必需的
type SubmitForm = Required<FormData>;

// 表单初始化时,所有字段都是可选的
type InitialForm = Partial<FormData>;

// 表单验证错误类型
type FormErrors = Partial<Record<keyof FormData, string>>;
// 类型:{ name?: string; email?: string; age?: string; agreeToTerms?: string; }

场景 3:函数类型复用

typescript
// 定义事件处理函数
function handleClick(event: MouseEvent, target: HTMLElement): void {
  // ...
}

// 复用参数类型
type ClickHandler = (event: Parameters<typeof handleClick>[0]) => void;

// 复用返回类型
type ClickResult = ReturnType<typeof handleClick>;
// 类型:void

// 创建事件映射
type EventHandlers = Record<string, (event: Event) => void>;

const handlers: EventHandlers = {
  click: (e) => console.log('clicked'),
  hover: (e) => console.log('hovered')
};

场景 4:配置对象类型

typescript
interface BaseConfig {
  host?: string;
  port?: number;
  timeout?: number;
}

// 开发环境配置:所有字段都是可选的
type DevConfig = Partial<BaseConfig>;

// 生产环境配置:所有字段都是必需的
type ProdConfig = Required<BaseConfig>;

// 只读配置:防止配置被修改
type ReadonlyConfig = Readonly<Required<BaseConfig>>;

工具类型组合使用

工具类型可以组合使用,创建更复杂的类型:

typescript
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
}

// 组合多个工具类型
type PublicUser = Readonly<Omit<User, 'password' | 'createdAt' | 'updatedAt'>>;
// 类型:{ readonly id: number; readonly name: string; readonly age: number; readonly email: string; }

// 创建用户输入类型(排除自动生成的字段,所有字段可选)
type CreateUserInput = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;

// 更新用户类型(排除 id 和自动生成的字段,所有字段可选)
type UpdateUserInput = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;

注意事项

注意

  1. 深度操作PartialRequiredReadonly 等工具类型只影响第一层属性,对于嵌套对象需要使用递归类型。

  2. 性能考虑:复杂的工具类型组合可能会影响类型检查性能,特别是在大型项目中。

  3. 类型推断:某些工具类型(如 ReturnTypeParameters)需要函数类型,不能直接用于值。

  4. 版本兼容性:部分工具类型(如 Awaited、字符串操作类型)需要特定版本的 TypeScript,使用时注意版本要求。

最佳实践

  1. 优先使用内置工具类型:内置工具类型经过优化,性能更好,语义更清晰。

  2. 组合使用:合理组合多个工具类型,避免重复定义类型。

  3. 命名清晰:使用工具类型时,给新类型起一个清晰的名字,提高代码可读性。

  4. 文档注释:对于复杂的工具类型组合,添加注释说明其用途。

相关链接

基于 VitePress 构建