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];
};
keyof T—— 获取 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 没有。工具类型是纯编译时构造——编译后所有类型信息被擦除,运行时零开销。但过于复杂的类型嵌套可能增加编译时间,实际影响可忽略。
📖 小节
- Partial/Required/Readonly 变换属性的可选性和只读性
- Pick/Omit 选择或排除指定属性——选更简洁的那个
- Record 快速创建键值对类型,配合字面量联合类型精确控制键
- Exclude/Extract/NonNullable 操作联合类型的成员
- ReturnType/Parameters/ConstructorParameters 提取函数的类型信息
- 工具类型的底层是映射类型 + keyof——理解原理就能自定义工具类型
📝 作业
- 基础题(难度⭐):定义
Todo接口(id、title、completed、createdAt),然后用工具类型创建:CreateTodo(创建时不需要id和时间)、UpdateTodo(更新时所有字段可选)、TodoPreview(只展示title和completed)。 - 进阶题(难度⭐⭐):自定义
Mutable<T>工具类型——移除所有 readonly 修饰符(用-readonly映射修饰符)。然后用Readonly<Config>创建只读配置,再用Mutable<Readonly<Config>>验证恢复可写。 - 挑战题(难度⭐⭐⭐):实现
PathKeys<T>工具类型——递归提取嵌套对象的所有属性路径。例如{ user: { name: string; address: { city: string } } }→"user" | "user.name" | "user.address" | "user.address.city"。