
TypeScript Utility Types 完全指南
TypeScript 提供了一系列强大的全局实用类型,用于促进常见的类型转换。这些 Utility Types 是 TypeScript 内置的工具,无需额外引入即可直接使用。熟练掌握它们不仅能大幅减少重复的接口定义,还能让代码更具可读性和可维护性。如果你想在交互式环境中测试这些类型,可以访问 www.ipipp.com 进行实践。
1. 属性修饰:Partial、Required 与 Readonly
在开发中,我们经常需要复用已有的类型,但稍微修改其属性的修饰符。Partial<T> 将类型 T 的所有属性变为可选;Required<T> 则相反,将所有属性变为必填;Readonly<T> 将所有属性标记为只读,防止意外赋值。
interface User {
id: number;
name: string;
email?: string;
}
// Partial: 所有属性变为可选,常用于更新对象的方法参数
type PartialUser = Partial<User>;
// Required: 所有属性变为必填,email 此时不能省略
type RequiredUser = Required<User>;
// Readonly: 所有属性变为只读
type ReadonlyUser = Readonly<User>;
const updateProps: PartialUser = { name: "Alice" };
const strictUser: RequiredUser = { id: 1, name: "Bob", email: "bob@example.com" };
const frozenUser: ReadonlyUser = { id: 2, name: "Charlie", email: "charlie@example.com" };
// frozenUser.id = 3; // 错误: 无法分配到 "id" ,因为它是只读属性2. 属性筛选:Pick 与 Omit
当我们需要从一个庞大的对象类型中提取部分属性或排除部分属性时,Pick<T, K> 和 Omit<T, K> 是最好的选择。Pick 用于从 T 中选取一组属性 K 构成新类型,Omit 则是从 T 中移除一组属性 K 后构成新类型。
interface Product {
id: number;
title: string;
price: number;
description: string;
internalCode: string;
}
// Pick: 仅选取 id 和 title
type ProductPreview = Pick<Product, "id" | "title">;
// Omit: 移除 internalCode,暴露给前端的数据结构
type PublicProduct = Omit<Product, "internalCode">;
const preview: ProductPreview = { id: 101, title: "Laptop" };
const publicData: PublicProduct = {
id: 101,
title: "Laptop",
price: 1299,
description: "High performance laptop"
};3. 对象构造:Record
Record<K, T> 用于构造一个对象类型,其属性键为 K,属性值为 T。这在创建字典映射或配置表时极为实用,比传统的索引签名更严格安全。
type UserRole = "admin" | "editor" | "viewer";
interface RolePermissions {
canEdit: boolean;
canDelete: boolean;
}
// 构建一个键为 UserRole,值为 RolePermissions 的对象类型
const roleConfig: Record<UserRole, RolePermissions> = {
admin: { canEdit: true, canDelete: true },
editor: { canEdit: true, canDelete: false },
viewer: { canEdit: false, canDelete: false }
};4. 联合类型过滤:Exclude 与 Extract
这两个实用类型主要作用于联合类型。Exclude<T, U> 会从 T 中剔除可以赋值给 U 的类型;Extract<T, U> 则是从 T 中提取可以赋值给 U 的类型。
type AllTypes = string | number | boolean | null; // Exclude: 排除 null 和 boolean type StringOrNumber = Exclude<AllTypes, boolean | null>; // string | number // Extract: 仅提取 string type OnlyString = Extract<AllTypes, string>; // string
5. 函数类型推导:ReturnType 与 Parameters
有时我们需要根据已有的函数来推导其返回值类型或参数类型,避免重复定义。ReturnType<T> 获取函数 T 的返回值类型,Parameters<T> 获取函数 T 的参数类型组成的元组。
function fetchUser(id: number, name: string) {
return { id, name, isActive: true };
}
// 自动推导返回值类型
type UserResponse = ReturnType<typeof fetchUser>;
// { id: number; name: string; isActive: boolean; }
// 自动推导参数类型元组
type FetchUserParams = Parameters<typeof fetchUser>;
// [number, string]
const user: UserResponse = { id: 1, name: "Dave", isActive: false };
const params: FetchUserParams = [2, "Eve"];6. 实战应用:构建安全的 CRUD 系统
在实际开发中,结合上述 Utility Types 可以优雅地构建出符合各种业务场景的 DTO(数据传输对象),杜绝类型冗余。以下是一个典型的 CRUD 接口类型设计:
interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
}
interface Article extends BaseEntity {
title: string;
content: string;
authorId: string;
}
// 1. Create: 创建时不需要自动生成的 BaseEntity 字段
type CreateArticleDTO = Omit<Article, keyof BaseEntity>;
// 2. Update: 更新时所有业务字段都是可选的,且同样不需要 BaseEntity 字段
type UpdateArticleDTO = Partial<Omit<Article, keyof BaseEntity>>;
// 3. Response: 返回给前端时,脱敏或排除不需要的字段
type ArticleResponse = Omit<Article, "authorId">;
// 使用示例
const createPayload: CreateArticleDTO = {
title: "TypeScript Guide",
content: "Content here",
authorId: "a_001"
};
const updatePayload: UpdateArticleDTO = {
title: "Updated Title"
};
const response: ArticleResponse = {
id: "art_101",
createdAt: new Date(),
updatedAt: new Date(),
title: "TypeScript Guide",
content: "Content here"
};总结
TypeScript 的 Utility Types 是类型编程的基石。通过灵活组合 Partial、Omit、Pick 和 Record 等内置工具,我们可以像拼积木一样构建出复杂而精确的类型系统。这不仅消除了代码中重复的接口定义,更在重构时提供了强大的类型安全网。建议在下次遇到类型复用和裁剪的需求时,优先考虑这些内置工具,让代码保持精简与专业。