内置工具类型
概述
TypeScript 提供了丰富的内置工具类型(Utility Types),这些工具类型基于映射类型和条件类型实现,可以帮助我们快速创建新的类型,而无需手动编写复杂的类型定义。工具类型是 TypeScript 类型系统的重要组成部分,掌握它们可以大大提高开发效率和代码质量。
对象属性操作工具类型
Partial<T>
将对象类型的所有属性变为可选。
实现原理:
type Partial<T> = {
[P in keyof T]?: T[P];
};使用示例:
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>
将对象类型的所有属性变为必需。
实现原理:
type Required<T> = {
[P in keyof T]-?: T[P];
};使用示例:
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>
将对象类型的所有属性变为只读。
实现原理:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};使用示例:
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。
实现原理:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};使用示例:
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。
实现原理:
type Omit<T, K extends keyof any> = {
[P in keyof T as P extends K ? never : P]: T[P];
};使用示例:
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; }提示
Omit 和 Pick 是互补的工具类型。Omit<T, K> 等价于 Pick<T, Exclude<keyof T, K>>。
类型创建工具类型
Record<K, T>
创建一个对象类型,其键为 K,值为 T。
实现原理:
type Record<K extends keyof any, T> = {
[P in K]: T;
};使用示例:
// 创建键值对类型
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>
提取函数类型的参数类型,返回一个元组类型。
实现原理:
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;使用示例:
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>
提取函数类型的返回类型。
实现原理:
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;使用示例:
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>
提取构造函数类型的参数类型。
使用示例:
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>
提取构造函数类型的实例类型。
使用示例:
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 类型:UserPromise 相关工具类型
Awaited<T>
提取 Promise 类型的内部类型(TypeScript 4.5+)。
使用示例:
// 提取 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>
将字符串字面量类型转换为大写。
type Greeting = "hello";
type UpperGreeting = Uppercase<Greeting>;
// 类型:"HELLO"
// 使用场景:创建常量类型
type EventName = "click" | "hover" | "focus";
type EventHandlerName = `on${Uppercase<EventName>}`;
// 类型:"onCLICK" | "onHOVER" | "onFOCUS"Lowercase<S>
将字符串字面量类型转换为小写。
type Status = "PENDING" | "APPROVED";
type LowerStatus = Lowercase<Status>;
// 类型:"pending" | "approved"Capitalize<S>
将字符串字面量类型的首字母转换为大写。
type Name = "john" | "jane";
type CapitalizedName = Capitalize<Name>;
// 类型:"John" | "Jane"
// 使用场景:创建属性名
type PropName = "name" | "age";
type GetterName = `get${Capitalize<PropName>}`;
// 类型:"getName" | "getAge"Uncapitalize<S>
将字符串字面量类型的首字母转换为小写。
type Method = "Get" | "Post";
type LowerMethod = Uncapitalize<Method>;
// 类型:"get" | "post"提示
这些字符串操作工具类型主要用于模板字面量类型中,可以创建更灵活的类型定义。更多内容请参考模板字面量类型。
类型操作工具类型
Exclude<T, U>
从类型 T 中排除可以赋值给 U 的类型。
实现原理:
type Exclude<T, U> = T extends U ? never : T;使用示例:
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>;
// 类型:stringExtract<T, U>
从类型 T 中提取可以赋值给 U 的类型。
实现原理:
type Extract<T, U> = T extends U ? T : never;使用示例:
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
>;
// 类型:() => voidNonNullable<T>
从类型 T 中排除 null 和 undefined。
实现原理:
type NonNullable<T> = T extends null | undefined ? never : T;使用示例:
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 请求和响应类型
// 定义完整的用户类型
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:表单类型处理
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:函数类型复用
// 定义事件处理函数
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:配置对象类型
interface BaseConfig {
host?: string;
port?: number;
timeout?: number;
}
// 开发环境配置:所有字段都是可选的
type DevConfig = Partial<BaseConfig>;
// 生产环境配置:所有字段都是必需的
type ProdConfig = Required<BaseConfig>;
// 只读配置:防止配置被修改
type ReadonlyConfig = Readonly<Required<BaseConfig>>;工具类型组合使用
工具类型可以组合使用,创建更复杂的类型:
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'>>;注意事项
注意
深度操作:
Partial、Required、Readonly等工具类型只影响第一层属性,对于嵌套对象需要使用递归类型。性能考虑:复杂的工具类型组合可能会影响类型检查性能,特别是在大型项目中。
类型推断:某些工具类型(如
ReturnType、Parameters)需要函数类型,不能直接用于值。版本兼容性:部分工具类型(如
Awaited、字符串操作类型)需要特定版本的 TypeScript,使用时注意版本要求。
最佳实践
优先使用内置工具类型:内置工具类型经过优化,性能更好,语义更清晰。
组合使用:合理组合多个工具类型,避免重复定义类型。
命名清晰:使用工具类型时,给新类型起一个清晰的名字,提高代码可读性。
文档注释:对于复杂的工具类型组合,添加注释说明其用途。
相关链接
- 映射类型 - 了解工具类型的实现原理
- 条件类型 - 学习条件类型在工具类型中的应用
- 泛型 - 理解泛型的基础概念
- 自定义工具类型 - 学习如何创建自定义工具类型
- TypeScript 官方文档 - 工具类型