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 时,null 和 undefined 不能赋给 {},因此会被交叉类型过滤。
五、自定义集合运算
// 并集
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。