TypeScript: TypeScript 工具类型 (Utility Types)

最后更新:2026-08-26

TypeScript 内置了十几个工具类型——它们是泛型类型编程的最佳实践,让你用一行代码完成常见的类型变换。

1. 属性变换类

(1) Partial——所有属性变可选

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

// Partial 让所有属性变成可选——适合更新操作
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }

function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}

let user: User = { id: 1, name: "Charlie", email: "xiao@example.com", age: 20 };
let updated = updateUser(user, { age: 21 });
// 只更新 age,其他属性不变

console.log(updated.age);   // 21
console.log(updated.name);  // "Charlie"(未变)

(2) Required——所有属性变必选

TYPESCRIPT
interface Config {
  host?: string;
  port?: number;
  debug?: boolean;
}

// Required 让所有属性变成必选——适合验证逻辑
type RequiredConfig = Required<Config>;
// { host: string; port: number; debug: boolean }

function validateConfig(config: RequiredConfig): void {
  console.log(`${config.host}:${config.port} (debug: ${config.debug})`);
}

(3) Readonly——所有属性变只读

TYPESCRIPT
interface Point {
  x: number;
  y: number;
}

// Readonly 让所有属性变成只读
type ReadonlyPoint = Readonly<Point>;
// { readonly x: number; readonly y: number }

let point: ReadonlyPoint = { x: 1, y: 2 };
// point.x = 10;  // ❌ 只读属性不可修改

// 常见用途:函数参数保护
function freezeConfig(config: Readonly<Config>): void {
  // config.host = "other";  // ❌ 不允许修改
  console.log("配置已冻结");
}

▶ 示例:CRUD 类型变换

TYPESCRIPT
interface Article {
  id: number;
  title: string;
  content: string;
  author: string;
  createdAt: Date;
  updatedAt: Date;
}

// 创建时:不需要 id 和时间戳
type CreateArticle = Omit<Article, "id" | "createdAt" | "updatedAt">;

// 更新时:所有字段可选
type UpdateArticle = Partial<Omit<Article, "id" | "createdAt">>;

// 列表展示:只显示部分字段
type ArticleSummary = Pick<Article, "id" | "title" | "author" | "createdAt">;

// 创建
let newArticle: CreateArticle = {
  title: "TypeScript入门",
  content: "TypeScript是JavaScript的超集...",
  author: "Charlie"
};

// 更新
let updateData: UpdateArticle = {
  title: "TypeScript进阶",
  content: "深入理解泛型..."
};

// 列表
let summary: ArticleSummary = {
  id: 1,
  title: "TypeScript入门",
  author: "Charlie",
  createdAt: new Date()
};

console.log("创建:" + newArticle.title);
console.log("更新:" + (updateData.title ?? "无变更"));
console.log("摘要:" + summary.title);
▶ 试一试

输出:

TEXT 📖 仅展示
创建:TypeScript入门
更新:TypeScript进阶
摘要:TypeScript入门

2. 属性选择类

(1) Pick——选取部分属性

TYPESCRIPT
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  role: string;
}

// Pick 选取指定的属性
type UserPublic = Pick<User, "id" | "name" | "email">;
// { id: number; name: string; email: string }

let publicProfile: UserPublic = {
  id: 1,
  name: "Charlie",
  email: "xiao@example.com"
};
// 没有 password 和 role —— 安全地暴露公开信息

(2) Omit——排除部分属性

TYPESCRIPT
// Omit 排除指定的属性(Pick 的反面)
type UserSafe = Omit<User, "password">;
// { id: number; name: string; email: string; role: string }

let safeUser: UserSafe = {
  id: 1,
  name: "Charlie",
  email: "xiao@example.com",
  role: "admin"
};
// password 被排除

(3) Pick vs Omit 选择

TYPESCRIPT
// 保留少数属性 → Pick 更简洁
type Mini = Pick<User, "id" | "name">;           // 2个属性 → Pick

// 排除少数属性 → Omit 更简洁
type NoPassword = Omit<User, "password">;         // 排除1个 → Omit

// 保留多数属性 → Omit 更简洁
type AlmostAll = Omit<User, "password">;          // 保留4个 → Omit

// 排除多数属性 → Pick 更简洁
type OnlyTwo = Pick<User, "id" | "name">;         // 排除3个 → Pick

3. 联合类型操作类

(1) Exclude——从联合类型中排除

TYPESCRIPT
type AllTypes = "a" | "b" | "c" | "d";

// Exclude 排除指定成员
type WithoutA = Exclude<AllTypes, "a">;        // "b" | "c" | "d"
type WithoutAB = Exclude<AllTypes, "a" | "b">; // "c" | "d"

(2) Extract——从联合类型中提取

TYPESCRIPT
type Mixed = string | number | boolean | null;

// Extract 提取指定成员
type OnlyString = Extract<Mixed, string>;       // string
type StringOrNumber = Extract<Mixed, string | number>;  // string | number

(3) NonNullable——排除 null 和 undefined

TYPESCRIPT
type MaybeString = string | null | undefined;

// NonNullable 排除 null 和 undefined
type DefiniteString = NonNullable<MaybeString>;  // string

▶ 示例:过滤无效值

TYPESCRIPT
type EventName = "click" | "focus" | "blur" | null | undefined;

// 排除 null 和 undefined
type ValidEvent = NonNullable<EventName>;  // "click" | "focus" | "blur"

// 只保留鼠标事件
type MouseEvent = Extract<ValidEvent, "click">;  // "click"

// 排除 click 以外的事件
type NonClick = Exclude<ValidEvent, "click">;    // "focus" | "blur"

function handleEvent(event: ValidEvent): void {
  console.log(`处理事件:${event}`);
}

handleEvent("click");   // ✅
handleEvent("focus");   // ✅
// handleEvent(null);   // ❌ NonNullable 已排除
▶ 试一试

4. 函数类型操作类

(1) ReturnType——获取函数返回值类型

TYPESCRIPT
function createUser(name: string, age: number) {
  return { name, age, active: true };
}

// ReturnType 获取返回值类型——不需要手写
type User = ReturnType<typeof createUser>;
// { name: string; age: number; active: boolean }

let user: User = { name: "Diana", age: 22, active: false };

(2) Parameters——获取函数参数类型元组

TYPESCRIPT
function register(name: string, email: string, age: number): void {}

// Parameters 获取参数类型
type RegisterParams = Parameters<typeof register>;
// [string, string, number]

let params: RegisterParams = ["Charlie", "xiao@example.com", 20];

(3) ConstructorParameters——获取构造函数参数类型

TYPESCRIPT
class Point {
  constructor(public x: number, public y: number, public z?: number) {}
}

type PointParams = ConstructorParameters<typeof Point>;
// [number, number, number?]

let args: PointParams = [1, 2];
let point = new Point(...args);

(4) InstanceType——获取构造函数实例类型

TYPESCRIPT
class Session {
  constructor(public token: string) {}
  isValid(): boolean { return this.token.length > 0; }
}

type SessionInstance = InstanceType<typeof Session>;
// 等价于 Session 类型

let session: SessionInstance = new Session("abc123");

5. Record——构建键值对类型

Record 是最常用的工具类型之一——快速创建"键→值"映射的类型:

TYPESCRIPT
// 基本用法:键和值类型
type StringMap = Record<string, string>;
let translations: StringMap = {
  hello: "你好",
  goodbye: "再见"
};

// 配合字面量联合类型——精确控制键
type Theme = "light" | "dark";
type ThemeColors = Record<Theme, { bg: string; text: string }>;

let themes: ThemeColors = {
  light: { bg: "#ffffff", text: "#333333" },
  dark: { bg: "#1a1a1a", text: "#e0e0e0" }
};

// 简写:用 Record 代替手写对象类型
type Scores = Record<"语文" | "数学" | "英语", number>;
let myScores: Scores = { 语文: 90, 数学: 95, 英语: 88 };

6. 工具类型的实现原理

理解工具类型的底层实现,有助于自定义工具类型:

(1) Partial 的实现

TYPESCRIPT
type Partial<T> = {
  [K in keyof T]?: T[K];
};

(2) Readonly 的实现

TYPESCRIPT
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

(3) Pick 的实现

TYPESCRIPT
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

(4) Omit 的实现

TYPESCRIPT
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

(5) Record 的实现

TYPESCRIPT
type Record<K extends keyof any, T> = {
  [P in K]: T;
};

▶ 示例:自定义工具类型

TYPESCRIPT
// DeepPartial——递归让所有层级变可选
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Config {
  server: {
    host: string;
    port: number;
  };
  database: {
    url: string;
    pool: {
      min: number;
      max: number;
    };
  };
}

type PartialConfig = DeepPartial<Config>;
// 所有层级的属性都变可选

let config: PartialConfig = {
  server: { host: "localhost" }  // port 可以省略
  // database 整个可以省略
};

// DeepReadonly——递归让所有层级变只读
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

type FrozenConfig = DeepReadonly<Config>;
// config.server.host = "other";  // ❌ 深层只读
▶ 试一试

❓ 常见问题

Q Partial 和可选属性有什么区别?
A 可选属性是接口定义时手动用 ? 标记的。Partial 是工具类型——它自动让已有类型的所有属性变可选。区别在于 Partial 是"基于已有类型派生",不需要重新定义接口。实际开发中,更新操作 UpdateUser = Partial<User> 是最常见的用法。
Q Pick 和 Omit 该选哪个?
A 看哪种更简洁——保留的属性少用 Pick,排除的属性少用 Omit。两者功能互补,选更短的那个。代码可读性才是关键——Omit<User, "password">Pick<User, "id" | "name" | "email" | "role"> 清晰得多。
Q ReturnType 能获取异步函数的返回类型吗?
A 异步函数返回 Promise,ReturnType<typeof asyncFn> 得到的是 Promise<T> 而不是 T。需要解包 Promise 用 Awaited<ReturnType<typeof asyncFn>>(TypeScript 4.5+内置了 Awaited 类型)。
Q 工具类型有性能影响吗?
A 没有。工具类型是纯编译时构造——编译后所有类型信息被擦除,运行时零开销。但过于复杂的类型嵌套可能增加编译时间,实际影响可忽略。

📖 小节

📝 作业

  1. 基础题(难度⭐):定义 Todo 接口(id、title、completed、createdAt),然后用工具类型创建:CreateTodo(创建时不需要id和时间)、UpdateTodo(更新时所有字段可选)、TodoPreview(只展示title和completed)。
  2. 进阶题(难度⭐⭐):自定义 Mutable<T> 工具类型——移除所有 readonly 修饰符(用 -readonly 映射修饰符)。然后用 Readonly<Config> 创建只读配置,再用 Mutable<Readonly<Config>> 验证恢复可写。
  3. 挑战题(难度⭐⭐⭐):实现 PathKeys<T> 工具类型——递归提取嵌套对象的所有属性路径。例如 { user: { name: string; address: { city: string } } }"user" | "user.name" | "user.address" | "user.address.city"
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏