讲解

TypeScript 内置了一批基于泛型和映射类型实现的工具类型,覆盖日常八成的类型变换需求,不用手写。按用途分四组。结构变换:Partial(所有属性变可选,更新接口的入参标配)、Required(全部变必填)、Readonly(全部只读)、Pick<T, K>(挑出部分属性)、Omit<T, K>(排除部分属性,派生「新建时不含 id」这类类型最顺手)、Record<K, V>(构造键为 K、值为 V 的对象类型)。

函数与返回值:ReturnType 拿函数返回值类型、Parameters 拿参数元组——给别人的函数写包装器时不用重复声明签名。字符串字面量变换:Uppercase、Lowercase、Capitalize、Uncapitalize,配合模板字面量类型可以做事件名推导(on + Capitalize)。联合处理:Exclude<U, X>(从联合中排除)、Extract<U, X>(提取匹配成员)、NonNullable(去掉 null 和 undefined)。

这些工具类型不是黑魔法,每一个都能用映射类型(下一章)手写出来。建议的策略是:先用熟内置的,遇到组合场景(如「深层 Partial」「把某个键变可选」)再读映射类型一章自己造。切忌为炫技嵌套五六层工具类型——类型是给同事看的,可读性优先。

示例

Partial 与 Omit 的典型业务场景:

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type CreateUserInput = Omit<User, "id">; // 新建时还没有 id
type UpdateUserInput = Partial<CreateUserInput>; // 更新时全可选

function updateUser(id: number, patch: UpdateUserInput): string {
  const fields = Object.keys(patch).join("、") || "(无修改)";
  return `用户 ${id} 更新了:${fields}`;
}

const input: CreateUserInput = { name: "小林", email: "lin@example.com", age: 30 };
console.log(input.name, updateUser(1, { age: 31 }));

Pick 与 Record:

interface Article {
  id: number;
  title: string;
  body: string;
  views: number;
}

type ArticleCard = Pick<Article, "id" | "title" | "views">;
type ViewCounter = Record<string, number>;

const card: ArticleCard = { id: 1, title: "工具类型", views: 1024 };
const counter: ViewCounter = { typescript: 3, docker: 1 };
console.log(`${card.title}(${card.views} 次阅读)`, counter);

ReturnType、Parameters 与 NonNullable:

function findUser(id: number): { id: number; name: string } | null {
  return id > 0 ? { id, name: "小林" } : null;
}

type FoundUser = NonNullable<ReturnType<typeof findUser>>; // 去掉 null
type FindUserArgs = Parameters<typeof findUser>; // [id: number]

function logFound(...args: FindUserArgs): void {
  const user: FoundUser | null = findUser(...args);
  if (user !== null) {
    console.log(`找到:${user.name}`);
  }
}

logFound(1);

常见坑

  • Partial 嵌套失效:Partial 只作用于第一层,嵌套对象内部仍然必填;深变换要自己写映射类型或引库。
  • Omit 的键不检查存在性:Omit<User, "naem"> 拼错也不报错(Omit 的键约束是宽松版本)——键名务必从 keyof 里选。
  • Record<string, T> 滥用:键集合已知时用具体键的 Record<"a" | "b", T>,能换来多余属性检查和补全。
  • 把工具类型当业务建模:领域模型该用 interface 清楚声明,工具类型适合「从模型派生视图/入参」,别反过来。

小结

Partial/Required/Readonly/Pick/Omit/Record 处理结构;ReturnType/Parameters 跟随函数;Exclude/Extract/NonNullable 处理联合。下一章揭开它们的实现原理:映射类型。