在 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 // 敏感字段
}
如果我们需要一个只包含 name
和 age
的用户信息类型(例如用于前端展示),可以使用 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 内部实现逻辑(基于 Pick
和 Exclude
):
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'
}
使用场景
- 空值处理 :确保变量不为
null
或undefined
,避免运行时错误(如Cannot read property 'x' of null
)。 - 类型断言简化 :替代
!
非空断言,通过类型层面确保值不为空。
总结
TypeScript 工具类型是提升类型处理效率的强大工具,它们覆盖了从属性选择、排除到函数类型提取等多种常见场景。本文介绍的 Pick
、Omit
、Partial
、Required
、Readonly
、Record
、Exclude
、Extract
、ReturnType
、Parameters
和 NonNullable
是开发中最常用的工具类型。
合理使用这些工具类型可以:
- 减少重复的类型定义,提升代码复用性;
- 确保类型一致性,降低手动维护类型的错误风险;
- 简化复杂类型转换逻辑,使代码更简洁易懂。
掌握这些工具类型后,你可以更高效地处理 TypeScript 类型系统,编写出更健壮、更易维护的代码。# 工具类型组合使用技巧
在实际开发中,单一工具类型往往无法满足复杂的类型转换需求。通过组合多个工具类型,我们可以实现更灵活、更强大的类型操作。以下是几个常见的组合示例:
- Partial & Pick:创建部分可选的子类型
需求:从 User
类型中选择 name
和 age
属性,并将它们变为可选(用于部分更新用户信息)。
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
- 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' // 报错:属性为只读
- 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 内置的工具类型,我们还可以根据业务需求创建自定义工具类型。自定义工具类型通常基于内置工具类型或条件类型实现。
- 实现类似 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 }
- 创建只包含函数属性的工具类型
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
// }
- 创建具有默认值的 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 可选
}
自定义工具类型可以极大地提升类型系统的灵活性,帮助我们解决特定业务场景下的类型问题。创建时应遵循单一职责原则,确保每个工具类型只做一件事,以便于组合和复用。