跳到主要内容

TypeScript工具类型指南

本文介绍属性修饰、结构变换和集合运算三类常用工具类型,包括原理、用法及自定义实现。

一、基础原理

TypeScript 工具类型主要建立在以下能力之上:

  • keyof T:取得对象类型 T 的属性名联合。
  • T[K]:取得属性 K 对应的值类型。
  • [K in keyof T]:遍历对象类型的属性,即映射类型。
  • T extends U ? X : Y:条件类型。
  • infer R:在条件类型中捕获待推断的类型。
  • never:空集合,常用于过滤联合类型成员。

二、属性修饰工具类型

属性修饰工具类型不会选择或删除属性,主要改变属性的可选性和只读性。

2.1 Partial<T>:全部属性变为可选

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

type UserPatch = Partial<User>;
// { id?: number; name?: string }

原理:映射全部属性,并使用 ? 添加可选修饰符。

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

2.2 Required<T>:全部属性变为必选

type CompleteUser = Required<{
id?: number;
name?: string;
}>;
// { id: number; name: string }

原理:-? 表示移除可选修饰符。

type MyRequired<T> = {
[K in keyof T]-?: T[K];
};

2.3 Readonly<T>:全部属性变为只读

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

2.4 Mutable<T>:全部属性变为可修改

这不是内置工具类型,需要自行实现。-readonly 表示移除只读修饰符。

type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};

以上类型只处理第一层属性。如需递归处理嵌套对象,可定义深层版本:

type DeepReadonly<T> = T extends (...args: any[]) => any
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;

三、结构工具类型

结构工具类型用于选择、排除、创建属性,或者提取函数及构造函数的组成部分。

3.1 Pick<T, K>:选择指定属性

type UserSummary = Pick<User, "id" | "name">;
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};

3.2 Omit<T, K>:排除指定属性

type UserWithoutId = Omit<User, "id">;

原理:先用 Exclude 从属性名集合中排除 K,再用 Pick 选择剩余属性。

type MyOmit<T, K extends PropertyKey> =
Pick<T, Exclude<keyof T, K>>;

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

3.3 Record<K, V>:创建键值结构

type Role = "admin" | "user";
type Permissions = Record<Role, string[]>;
// { admin: string[]; user: string[] }
type MyRecord<K extends PropertyKey, V> = {
[P in K]: V;
};

3.4 函数结构提取

type Fn = (name: string, age: number) => boolean;

type Args = Parameters<Fn>;
// [name: string, age: number]

type Result = ReturnType<Fn>;
// boolean

其核心是条件类型和 infer

type MyParameters<T> =
T extends (...args: infer P) => any ? P : never;

type MyReturnType<T> =
T extends (...args: any[]) => infer R ? R : never;

3.5 构造函数结构提取

class Account {
constructor(public id: number, public name: string) {}
}

type ConstructorArgs = ConstructorParameters<typeof Account>;
// [id: number, name: string]

type AccountInstance = InstanceType<typeof Account>;
// Account

四、集合工具类型

联合类型可视为类型成员的集合。条件类型作用于裸类型参数时,会对联合成员进行分发。

4.1 Exclude<T, U>:差集

type Result = Exclude<"a" | "b" | "c", "a" | "c">;
// "b"

type MyExclude<T, U> = T extends U ? never : T;

每个 T 成员分别接受判断;匹配 U 的成员变为 never,最终从联合类型中消失。

4.2 Extract<T, U>:交集

type Result = Extract<"a" | "b" | 1, string>;
// "a" | "b"

type MyExtract<T, U> = T extends U ? T : never;

4.3 NonNullable<T>:排除空值

type Result = NonNullable<string | null | undefined>;
// string

type MyNonNullable<T> = T & {};

在开启 strictNullChecks 时,nullundefined 不能赋给 {},因此会被交叉类型过滤。

五、自定义集合运算

// 并集
type Union<A, B> = A | B;

// A 相对于 B 的差集
type Difference<A, B> = Exclude<A, B>;

// 交集
type Intersection<A, B> = Extract<A, B>;

// 对称差集:只存在于其中一个集合的成员
type SymmetricDifference<A, B> =
Exclude<A | B, Extract<A, B>>;

// 补集:在全集 All 中排除集合 A
type Complement<All, A> = Exclude<All, A>;

使用示例:

type A = "a" | "b";
type B = "b" | "c";

type U = Union<A, B>; // "a" | "b" | "c"
type D = Difference<A, B>; // "a"
type I = Intersection<A, B>; // "b"
type S = SymmetricDifference<A, B>; // "a" | "c"
type C = Complement<"a" | "b" | "c", A>; // "c"

六、组合使用

让某个属性变为可选,其他属性保持不变:

type OptionalProperty<T, K extends keyof T> =
Omit<T, K> & Partial<Pick<T, K>>;

type OptionalName = OptionalProperty<User, "name">;
// 等价于 { id: number; name?: string }

让某个属性变为必选:

type RequiredProperty<T, K extends keyof T> =
Omit<T, K> & Required<Pick<T, K>>;

总结:属性修饰依赖映射类型修饰符,结构变换依赖 keyof、映射类型和 infer,集合运算依赖联合类型、条件类型的分发行为以及 never