TypeScript 工具类型(Utility Types)

在 TypeScript 开发中,我们经常需要基于已有类型创建新的类型。手动重复定义这些类型不仅繁琐,还可能导致不一致。TypeScript 内置了一系列工具类型(Utility Types) ,它们是泛型类型,用于执行常见的类型转换操作。这些工具类型可以帮助我们快速实现类型截取、修改、筛选等功能,极大提升开发效率和类型安全性。本文将详细讲解常用的 TypeScript 工具类型,包括它们的作用、语法、代码示例和使用场景。

Pick < T, K >:选择类型属性

作用

Pick < T, K > 用于从已有类型 T选择指定的属性 K ,创建一个只包含这些属性的新类型。其中,K 必须是 T 的属性名组成的联合类型。

语法

TypeScript 内部实现逻辑:

scala 复制代码
type Pick<T, K extends keyof T> = { [P in K]: T[P] }

代码示例

假设我们有一个 User 接口,包含用户的完整信息:

typescript 复制代码
interface User {
  id: number
  name: string
  age: number
  email: string
  password: string // 敏感字段
}

如果我们需要一个只包含 nameage 的用户信息类型(例如用于前端展示),可以使用 Pick

go 复制代码
// 从 User 中选择 'name' 和 'age' 属性
type UserNameAge = Pick<User, 'name' | 'age'>
// UserNameAge 等价于:
// {
//   name: string
//   age: number
// }
// 使用示例
const userInfo: UserNameAge = {
  name: 'Alice',
  age: 30
}

使用场景

  • 数据展示:从完整数据类型中提取需要展示的字段,过滤敏感或无关属性(如密码、内部 ID 等)。
  • 类型复用:基于已有类型创建精简版类型,避免重复定义。

Omit < T, K >:排除类型属性

作用

Omit < T, K >Pick 相反,用于从已有类型 T排除指定的属性 K ,创建一个包含剩余属性的新类型。K 同样是 T 的属性名组成的联合类型。

语法

TypeScript 内部实现逻辑(基于 PickExclude):

scala 复制代码
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>

代码示例

继续使用上述 User 接口,如果需要排除敏感字段 password,保留其他所有属性(例如用于 API 响应返回):

go 复制代码
// 从 User 中排除 'password' 属性
type PublicUser = Omit<User, 'password'>
// PublicUser 等价于:
// {
//   id: number
//   name: string
//   age: number
//   email: string
// }
// 使用示例
const publicUser: PublicUser = {
  id: 1,
  name: 'Alice',
  age: 30,
  email: 'alice@example.com'
}

使用场景

  • 数据脱敏:排除敏感字段(如密码、token),确保返回给前端的数据安全。
  • 简化类型:移除不需要的属性,保留核心字段。

Partial < T >:将属性变为可选

作用

Partial < T > 用于将类型 T所有属性转换为可选属性,创建一个新类型。这意味着新类型的每个属性都可以被省略。

语法

TypeScript 内部实现逻辑:

r 复制代码
type Partial<T> = { [P in keyof T]?: T[P] }

代码示例

假设我们需要一个用户信息更新函数,允许只传递需要修改的字段(而非所有字段):

php 复制代码
interface User {
  id: number
  name: string
  age: number
  email: string
}
// 将 User 的所有属性变为可选
type PartialUser = Partial<User>
// PartialUser 等价于:
// {
//   id?: number
//   name?: string
//   age?: number
//   email?: string
// }
// 更新函数示例
function updateUser(user: User, changes: PartialUser): User {
  return { ...user, ...changes }
}
// 使用示例:只更新 age 和 email
const originalUser: User = {
  id: 1,
  name: 'Alice',
  age: 30,
  email: 'old@example.com'
}
const updatedUser = updateUser(originalUser, {
  age: 31,
  email: 'new@example.com'
})
// updatedUser: { id: 1, name: 'Alice', age: 31, email: 'new@example.com' }

使用场景

  • 数据更新:函数参数只需要部分字段(如更新操作),避免必须传递完整对象。
  • 可选配置:定义可选的配置项类型,允许用户只传递需要自定义的配置。

Required < T >:将属性变为必选

作用

Required < T >Partial 相反,用于将类型 T所有可选属性转换为必选属性,确保新类型的每个属性都必须被提供。

语法

TypeScript 内部实现逻辑(-? 表示移除可选修饰符):

r 复制代码
type Required<T> = { [P in keyof T]-?: T[P] }

代码示例

假设我们有一个部分字段可选的类型,但在某些场景下需要所有字段必选(如表单提交):

typescript 复制代码
// 部分字段可选的类型
type OptionalUser = {
  id: number // 必选
  name?: string // 可选
  age?: number // 可选
}
// 将所有可选属性变为必选
type RequiredUser = Required<OptionalUser>
// RequiredUser 等价于:
// {
//   id: number
//   name: string
//   age: number
// }
// 使用示例:表单提交必须包含所有字段
const submitUser: RequiredUser = {
  id: 1,
  name: 'Alice', // 必须提供,否则报错
  age: 30 // 必须提供,否则报错
}

使用场景

  • 表单验证:确保表单提交的数据包含所有必填字段。
  • 严格模式:在需要完整数据的场景下,强制要求所有属性都被提供。

Readonly < T >:将属性变为只读

作用

Readonly < T > 用于将类型 T所有属性转换为 只读 属性,创建后无法修改属性值。

语法

TypeScript 内部实现逻辑:

r 复制代码
type Readonly<T> = { readonly [P in keyof T]: T[P] }

代码示例

定义一个常量配置对象,确保其属性不会被意外修改:

typescript 复制代码
interface AppConfig {
  apiUrl: string
  timeout: number
}
// 将所有属性变为只读
type ReadonlyConfig = Readonly<AppConfig>
// 使用示例
const config: ReadonlyConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
}
config.apiUrl = 'https://new-api.com' // 报错:无法分配到 "apiUrl" ,因为它是只读属性

使用场景

  • 常量数据:定义不可修改的配置、常量或固定数据(如国家列表、状态码映射)。
  • 不可变对象:确保对象在创建后不被修改,避免副作用(如 React 中的 props)。

Record < K, T >:定义键值对类型

作用

Record < K, T > 用于创建一个键类型为 K 值类型 T 的对象类型K 通常是字符串或联合类型(表示允许的键名),T 是值的类型。

语法

TypeScript 内部实现逻辑:

scala 复制代码
type Record<K extends keyof any, T> = { [P in K]: T }

代码示例

创建一个用户角色到权限的映射表(键为角色名,值为权限数组):

typescript 复制代码
// 定义允许的角色类型
type Role = 'admin' | 'editor' | 'viewer'
// 定义权限类型
type Permissions = string[]
// 创建角色-权限映射类型
type RolePermissions = Record<Role, Permissions>
// 使用示例
const rolePermissions: RolePermissions = {
  admin: ['create', 'read', 'update', 'delete'],
  editor: ['read', 'update'],
  viewer: ['read']
}
// 访问示例
console.log(rolePermissions.editor) // ['read', 'update']

使用场景

  • 字典/映射表:定义键值对结构(如状态码到消息的映射、地区到语言的映射)。
  • 批量数据类型:统一管理同类型值的集合(如多个用户的配置信息)。

Exclude < T, U >:排除联合类型成员

作用

Exclude < T, U > 用于从联合类型 T 中排除所有可赋值给类型 U 的成员,返回剩余成员组成的新联合类型。

语法

TypeScript 内部实现逻辑(条件类型):

r 复制代码
type Exclude<T, U> = T extends U ? never : T

代码示例

从事件类型中排除不需要的事件:

lua 复制代码
// 定义所有可能的事件类型
type EventType = 'click' | 'scroll' | 'mousemove' | 'resize'
// 排除 'scroll' 事件
type NonScrollEvents = Exclude<EventType, 'scroll'>
// NonScrollEvents 等价于:'click' | 'mousemove' | 'resize'
// 使用示例
function handleEvent(event: NonScrollEvents) {
  console.log('Handling event:', event)
}
handleEvent('click') // 正确
handleEvent('scroll') // 报错:'scroll' 不在 NonScrollEvents 中

使用场景

  • 联合类型筛选:从联合类型中移除不需要的类型(如排除特定事件、状态等)。
  • 类型减法:实现类似"类型减法"的效果,精简联合类型。

Extract < T, U >:提取联合类型成员

作用

Extract < T, U >Exclude 相反,用于从联合类型 T 中提取所有可赋值给类型 U 的成员,返回提取后的新联合类型。

语法

TypeScript 内部实现逻辑(条件类型):

r 复制代码
type Extract<T, U> = T extends U ? T : never

代码示例

从事件类型中提取鼠标相关事件:

lua 复制代码
// 定义所有可能的事件类型
type EventType = 'click' | 'scroll' | 'mousemove' | 'keydown'
// 提取鼠标相关事件('click' 和 'mousemove')
type MouseEvents = Extract<EventType, 'click' | 'mousemove'>
// MouseEvents 等价于:'click' | 'mousemove'
// 使用示例
function handleMouseEvent(event: MouseEvents) {
  console.log('Handling mouse event:', event)
}
handleMouseEvent('mousemove') // 正确
handleMouseEvent('keydown') // 报错:'keydown' 不在 MouseEvents 中

使用场景

  • 联合类型筛选:从联合类型中保留需要的类型(如提取特定事件、状态等)。
  • 类型交集:获取两个联合类型的交集成员。

ReturnType < T >:获取函数返回值类型

作用

ReturnType < T > 用于获取函数类型 T 的返回值类型,适用于需要复用函数返回值类型的场景。

语法

TypeScript 内部实现逻辑(infer R 表示推断返回值类型):

scala 复制代码
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

代码示例

获取 API 请求函数的返回值类型:

csharp 复制代码
// 定义一个返回用户数据的函数
function fetchUser() {
  return {
    id: 1,
    name: 'Alice',
    age: 30,
    isActive: true
  }
}
// 获取 fetchUser 的返回值类型
type UserData = ReturnType<typeof fetchUser>
// UserData 等价于:
// {
//   id: number
//   name: string
//   age: number
//   isActive: boolean
// }
// 使用示例:定义接收用户数据的变量
const user: UserData = {
  id: 2,
  name: 'Bob',
  age: 25,
  isActive: false
}

使用场景

  • API 响应类型:直接复用请求函数的返回值类型,避免手动定义重复的响应类型。
  • 函数结果处理:确保处理函数返回值的变量类型与函数返回值一致。

Parameters < T >:获取函数参数类型

作用

Parameters < T > 用于获取函数类型 T 的参数类型,返回一个元组类型,元组的每个元素对应函数参数的类型。

语法

TypeScript 内部实现逻辑(infer P 表示推断参数类型元组):

scala 复制代码
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

代码示例

获取函数参数类型并安全传递参数:

typescript 复制代码
// 定义一个带参数的函数
function greet(name: string, age: number): string {
  return `Hello, ${name}, you are ${age} years old`
}
// 获取 greet 函数的参数类型元组
type GreetParams = Parameters<typeof greet>
// GreetParams 等价于:[string, number]
// 使用示例:类型安全地传递参数
const params: GreetParams = ['Bob', 25] // 元组类型必须严格匹配参数顺序和类型
const message = greet(...params) // 正确调用,等价于 greet('Bob', 25)

使用场景

  • 参数复用:获取函数参数类型,用于定义传递参数的变量或数组(如测试用例中的参数列表)。
  • 函数重载:在复杂函数重载场景中,统一管理参数类型。

NonNullable < T >:排除 null 和 undefined

作用

NonNullable < T > 用于从类型 T排除 null undefined,返回一个不包含这两个类型的新类型。

语法

TypeScript 内部实现逻辑:

typescript 复制代码
type NonNullable<T> = T extends null | undefined ? never : T

代码示例

处理可能为 null 的 DOM 元素类型:

typescript 复制代码
// 获取 DOM 元素(可能为 null)
const element = document.getElementById('app') // 类型:HTMLElement | null
// 排除 null 类型
type ElementType = NonNullable<typeof element> // 类型:HTMLElement
// 使用示例:确保元素不为 null
if (element) {
  const safeElement: ElementType = element // 类型安全
  safeElement.innerHTML = 'Hello World'
}

使用场景

  • 空值处理 :确保变量不为 nullundefined,避免运行时错误(如 Cannot read property 'x' of null)。
  • 类型断言简化 :替代 ! 非空断言,通过类型层面确保值不为空。

总结

TypeScript 工具类型是提升类型处理效率的强大工具,它们覆盖了从属性选择、排除到函数类型提取等多种常见场景。本文介绍的 PickOmitPartialRequiredReadonlyRecordExcludeExtractReturnTypeParametersNonNullable 是开发中最常用的工具类型。

合理使用这些工具类型可以:

  • 减少重复的类型定义,提升代码复用性;
  • 确保类型一致性,降低手动维护类型的错误风险;
  • 简化复杂类型转换逻辑,使代码更简洁易懂。

掌握这些工具类型后,你可以更高效地处理 TypeScript 类型系统,编写出更健壮、更易维护的代码。# 工具类型组合使用技巧

在实际开发中,单一工具类型往往无法满足复杂的类型转换需求。通过组合多个工具类型,我们可以实现更灵活、更强大的类型操作。以下是几个常见的组合示例:

  1. Partial & Pick:创建部分可选的子类型

需求:从 User 类型中选择 nameage 属性,并将它们变为可选(用于部分更新用户信息)。

typescript 复制代码
interface User {
  id: number
  name: string
  age: number
  email: string
}
// 组合 Partial 和 Pick
type UpdateUserInfo = Partial<Pick<User, 'name' | 'age'>>
// UpdateUserInfo 等价于:
// {
//   name?: string
//   age?: number
// }
// 使用示例:只更新需要修改的字段
function updateUserInfo(user: User, changes: UpdateUserInfo): User {
  return { ...user, ...changes }
}
const user = { id: 1, name: 'Alice', age: 30, email: 'alice@example.com' }
const updatedUser = updateUserInfo(user, { age: 31 }) // 只更新 age
  1. Omit & Readonly:创建排除特定属性的只读类型

需求:从 Config 类型中排除 secretKey 属性,并将剩余属性设为只读(用于公开配置)。

typescript 复制代码
interface Config {
  apiUrl: string
  timeout: number
  secretKey: string
}
// 组合 Omit 和 Readonly
type PublicConfig = Readonly<Omit<Config, 'secretKey'>>
// PublicConfig 等价于:
// {
//   readonly apiUrl: string
//   readonly timeout: number
// }
// 使用示例:公开配置不可修改
const publicConfig: PublicConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
}
publicConfig.apiUrl = 'new-url' // 报错:属性为只读
  1. Exclude & Extract:筛选联合类型

需求:从事件类型中排除鼠标事件,并提取键盘事件。

bash 复制代码
type AllEvents = 'click' | 'mousemove' | 'keydown' | 'keyup' | 'scroll'
// 排除鼠标事件
type NonMouseEvents = Exclude<AllEvents, 'click' | 'mousemove'>
// 'keydown' | 'keyup' | 'scroll'
// 提取键盘事件
type KeyboardEvents = Extract<NonMouseEvents, 'keydown' | 'keyup'>
// 'keydown' | 'keyup'

自定义工具类型

除了 TypeScript 内置的工具类型,我们还可以根据业务需求创建自定义工具类型。自定义工具类型通常基于内置工具类型或条件类型实现。

  1. 实现类似 Omit 的自定义工具类型
scala 复制代码
// 自定义 MyOmit(功能与 Omit 一致)
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
// 使用示例
interface User {
  id: number
  name: string
  age: number
}
type UserWithoutAge = MyOmit<User, 'age'>
// { id: number; name: string }
  1. 创建只包含函数属性的工具类型
typescript 复制代码
// 提取类型中所有函数属性
type FunctionProperties<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never
}[keyof T]
// 使用示例
interface UserService {
  id: number
  getUser: (id: number) => User
  updateUser: (user: User) => User
  isValid: boolean
}
// 提取所有函数属性名
type UserServiceMethods = FunctionProperties<UserService>
// 'getUser' | 'updateUser'
// 只保留函数属性的类型
type UserServiceFunctions = Pick<UserService, UserServiceMethods>
// {
//   getUser: (id: number) => User
//   updateUser: (user: User) => User
// }
  1. 创建具有默认值的 Partial 类型
scala 复制代码
// 自定义 WithDefaults,为指定属性提供默认值
type WithDefaults<T, D extends Partial<T>> = T & {
  [K in keyof D]-?: T[K]
}
// 使用示例
interface Options {
  a?: number
  b?: string
  c?: boolean
}
// 为 a 和 b 提供默认值
type OptionsWithDefaults = WithDefaults<Options, { a: number; b: string }>
// 使用示例:a 和 b 变为必选,c 仍为可选
const options: OptionsWithDefaults = {
  a: 10, // 必须提供
  b: 'default', // 必须提供
  // c 可选
}

自定义工具类型可以极大地提升类型系统的灵活性,帮助我们解决特定业务场景下的类型问题。创建时应遵循单一职责原则,确保每个工具类型只做一件事,以便于组合和复用。

相关推荐
難釋懷17 小时前
TypeScript类
前端·typescript
杰哥焯逊18 小时前
基于TS封装的高德地图JS APi2.0实用工具(包含插件类型,基础类型)...持续更新
前端·javascript·typescript
工业甲酰苯胺2 天前
TypeScript枚举类型应用:前后端状态码映射的最简方案
javascript·typescript·状态模式
土豆骑士3 天前
简单理解Typescript 装饰器
前端·typescript
ttod_qzstudio3 天前
彻底移除 HTML 元素:element.remove() 的本质与最佳实践
前端·javascript·typescript·html
张志鹏PHP全栈3 天前
TypeScript 第七天,TypeScript和webpack如何搭配使用
前端·typescript
Jinxiansen02113 天前
TypeScript 中的内置工具类型(Utility Type)
javascript·ubuntu·typescript
寻觅~流光3 天前
封装---优化try..catch错误处理方式
开发语言·前端·javascript·typescript
军军君013 天前
基于Springboot+UniApp+Ai实现模拟面试小工具四:后端项目基础框架搭建下
spring boot·spring·面试·elementui·typescript·uni-app·mybatis