Back

5个你应该了解的 TypeScript 实用工具类型

5个你应该了解的 TypeScript 实用工具类型

当你发现实用工具类型——这些内置的辅助工具能够在不重写现有类型的情况下对其进行转换时,TypeScript 的类型系统才真正展现出强大的力量。如果你曾经发现自己在重复定义接口或在类型修改上遇到困难,这五个基本的实用工具类型将改变你编写 TypeScript 代码的方式。

核心要点

  • 实用工具类型可以在不重写现有类型的情况下对其进行转换,减少代码重复
  • Partial 使所有属性变为可选,非常适合更新操作
  • Required 强制所有属性必填,确保数据结构的完整性
  • Pick 和 Omit 通过选择或排除属性来帮助创建专注的接口
  • Readonly 防止意外的数据变更,对不可变数据模式至关重要

什么是 TypeScript 实用工具类型以及它们的重要性

实用工具类型是 TypeScript 预构建的泛型类型,通过转换现有类型来构造新类型。与其手动创建接口的变体,这些实用工具让你能够通过特定修改派生新类型——使你的代码更易维护并减少类型重复。

我们将探索五个解决常见实际问题的基础实用工具类型:PartialRequiredPickOmitReadonly。每一个都解决了你在 TypeScript 开发中每天遇到的特定场景。

Partial<T>:在 TypeScript 中使所有属性变为可选

Partial 实用工具类型将类型的所有属性转换为可选的,非常适合只修改特定字段的更新操作。

何时使用 Partial 进行表单更新

考虑一个用户资料更新场景,用户可以更改单个字段而无需发送整个对象:

interface User {
  id: string;
  name: string;
  email: string;
  avatar: string;
  lastLogin: Date;
}

function updateUserProfile(userId: string, updates: Partial<User>) {
  // 只更新提供的字段
  return api.patch(`/users/${userId}`, updates);
}

// 使用方式 - 只更新邮箱
updateUserProfile("123", { email: "new@email.com" });

Partial 类型的真实代码示例

React 状态更新从 Partial 中受益匪浅:

const [user, setUser] = useState<User>(initialUser);

const updateUser = (updates: Partial<User>) => {
  setUser(prev => ({ ...prev, ...updates }));
};

// 只更新特定字段
updateUser({ name: "Jane Doe", avatar: "new-avatar.jpg" });

Required<T>:在 TypeScript 中强制所有属性必填

Required 与 Partial 相反——它使所有可选属性变为必填,确保数据结构的完整性。

将可选属性转换为必填属性

这在配置验证中非常有价值:

interface DatabaseConfig {
  host?: string;
  port?: number;
  username?: string;
  password?: string;
}

// 确保所有配置值在连接前都存在
type ValidatedConfig = Required<DatabaseConfig>;

function connectDatabase(config: ValidatedConfig) {
  // 保证所有属性都存在
  return createConnection(config);
}

Required 类型的实际应用

表单提交通常需要完整的数据:

interface RegistrationForm {
  username?: string;
  email?: string;
  password?: string;
  terms?: boolean;
}

type CompleteRegistration = Required<RegistrationForm>;

function submitRegistration(data: CompleteRegistration) {
  // 所有字段都必须填写
  api.post('/register', data);
}

Pick<T, K>:从类型中选择特定属性

Pick 通过只选择指定的属性来创建新类型,非常适合创建专注的接口。

使用 Pick 创建专注的类型子集

为特定组件提取所需的内容:

interface Article {
  id: string;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  tags: string[];
}

// 卡片组件只需要这些字段
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;

function ArticleCard({ article }: { article: ArticlePreview }) {
  return (
    <div>
      <h3>{article.title}</h3>
      <p>by {article.author}</p>
    </div>
  );
}

Pick vs Omit:选择正确的方法

当你需要少数属性时使用 Pick,当排除少数属性时使用 Omit:

// Pick:当你需要从大型类型中获取 2-3 个属性时
type UserSummary = Pick<User, 'id' | 'name'>;

// Omit:当你需要大部分属性但排除少数几个时
type PublicUser = Omit<User, 'password'>;

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

Omit 通过排除指定属性来创建类型,非常适合移除敏感或内部字段。

使用 Omit 移除敏感数据

在 API 响应中保护敏感信息:

interface UserAccount {
  id: string;
  email: string;
  password: string;
  creditCard: string;
  publicProfile: boolean;
}

// 为公共 API 移除敏感字段
type PublicUserData = Omit<UserAccount, 'password' | 'creditCard'>;

function getPublicProfile(userId: string): Promise<PublicUserData> {
  return api.get(`/users/${userId}/public`);
}

Omit 实用工具类型最佳实践

与其他实用工具结合使用以实现强大的模式:

// 移除内部字段并使剩余字段可选
type UserUpdatePayload = Partial<Omit<User, 'id' | 'createdAt'>>;

Readonly<T>:在 TypeScript 中创建不可变类型

Readonly 使所有属性不可变,防止意外变更并强制数据完整性。

使用 Readonly 防止意外变更

在状态管理和配置中至关重要:

interface AppConfig {
  apiEndpoint: string;
  version: string;
  features: string[];
}

type ImmutableConfig = Readonly<AppConfig>;

const config: ImmutableConfig = {
  apiEndpoint: "https://api.example.com",
  version: "1.0.0",
  features: ["auth", "payments"]
};

// TypeScript 错误:无法分配给 'apiEndpoint',因为它是只读属性
config.apiEndpoint = "https://new-api.com"; // ❌

Readonly 属性和 React Props

确保 props 保持不变:

type UserCardProps = Readonly<{
  user: User;
  onSelect: (id: string) => void;
}>;

function UserCard({ user, onSelect }: UserCardProps) {
  // Props 在组件内是不可变的
  return <div onClick={() => onSelect(user.id)}>{user.name}</div>;
}

结合多个实用工具类型实现高级模式

当实用工具类型结合使用时变得更加强大:

interface DatabaseRecord {
  id: string;
  createdAt: Date;
  updatedAt: Date;
  deletedAt?: Date;
  data: Record<string, unknown>;
}

// 为新记录创建类型:无时间戳,部分数据
type NewRecord = Omit<DatabaseRecord, 'id' | 'createdAt' | 'updatedAt'> & {
  data: Partial<DatabaseRecord['data']>;
};

// 更新类型:除 ID 外所有内容都可选
type UpdateRecord = Partial<Omit<DatabaseRecord, 'id'>> & Pick<DatabaseRecord, 'id'>;

结论

这五个实用工具类型——PartialRequiredPickOmitReadonly——构成了有效 TypeScript 开发的基础。它们消除了重复的类型定义,强化了类型安全,并使你的代码更易维护。

开始将这些模式融入到你的代码库中。从用于更新操作的 Partial 开始,使用 Omit 创建安全的公共接口,并利用 Readonly 来防止错误。随着你越来越熟练,将它们结合起来创建复杂的类型转换,完美匹配你应用程序的需求。

常见问题

是的,你可以嵌套实用工具类型来创建复杂的转换。例如,Partial 和 Omit 可以很好地配合使用,创建排除某些字段同时使其他字段可选的更新载荷。

不会,实用工具类型纯粹是编译时构造。TypeScript 在编译过程中会移除所有类型信息,因此实用工具类型没有运行时开销。

接口扩展通过添加属性创建新类型,而实用工具类型转换现有类型。实用工具类型为修改你无法控制的类型或创建现有类型的变体提供了更多灵活性。

是的,TypeScript 允许你使用映射类型、条件类型和模板字面量类型创建自定义实用工具类型。从内置实用工具开始,当你识别出重复模式时再创建自定义的。

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay