TypeScript 工具类型

属性修饰工具类型

Copy
type Partial<T> = {
[P in keyof T]?: T[P];
};

type DeepPartial<T extends object> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

type Required<T> = {
[P in keyof T]-?: T[P];
};

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

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

type DeepReadonly<T extends object> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

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

type DeepMutable<T extends object> = {
-readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K];
};

Copy
type NonNullable<T> = T extends null | undefined ? never : T;

export type DeepNonNullable<T extends object> = {
[K in keyof T]: T[K] extends object
? DeepNonNullable<T[K]>
: NonNullable<T[K]>;
};

type Nullable<T> = T | null;

type DeepNullable<T extends object> = {
[K in keyof T]: T[K] extends object ? DeepNullable<T[K]> : Nullable<T[K]>;
};

将复杂的工具类型，拆解为由基础工具类型、类型工具的组合 (拆分 - 处理 - 组合)

Copy
type Flatten<T> = { [K in keyof T]: T[K] };

type MarkPropsAsOptional<
T extends object,
K extends keyof T = keyof T
> = Flatten<Partial<Pick<T, K>> & Omit<T, K>>;

type MarkPropsAsRequired<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Required<Pick<T, K>>>;

type MarkPropsAsReadonly<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Readonly<Pick<T, K>>>;

type MarkPropsAsMutable<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Mutable<Pick<T, K>>>;

type MarkPropsAsNullable<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & Nullable<Pick<T, K>>>;

type MarkPropsAsNonNullable<
T extends object,
K extends keyof T = keyof T
> = Flatten<Omit<T, K> & NonNullable<Pick<T, K>>>;

结构工具类型

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

type Dictionary<T> = {
[index: string]: T;
};

type NumericDictionary<T> = {
[index: number]: T;
};

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

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

Exclude<A, B> 的结果就是联合类型 A 中不存在于 B 中的部分

基于键值类型的 Pick 与 Omit

Copy
type ExpectedPropKeys<T extends object, ValueType> = {
[Key in keyof T]-?: T[Key] extends ValueType ? Key : never;
}[keyof T];

type PickByValueType<T extends object, ValueType> = Pick<
T,
ExpectedPropKeys<T, ValueType>
>;

Copy
type FilteredPropKeys<T extends object, ValueType> = {
[Key in keyof T]-?: T[Key] extends ValueType ? never : Key;
}[keyof T];

type OmitByValueType<T extends object, ValueType> = Pick<
T,
FilteredPropKeys<T, ValueType>
>;

Copy
type StrictConditional<A, B, Resolved, Rejected, Fallback = never> = [
A
] extends [B]
? [B] extends [A]
? Resolved
: Rejected
: Fallback;

type StrictValueTypeFilter<
T extends object,
ValueType,
Positive extends boolean = true
> = {
[Key in keyof T]-?: StrictConditional<
ValueType,
T[Key],
// 为了避免嵌套太多工具类型，这里就不使用 Conditional 了
Positive extends true ? Key : never,
Positive extends true ? never : Key,
Positive extends true ? never : Key
>;
}[keyof T];

type StrictPickByValueType<T extends object, ValueType> = Pick<
T,
StrictValueTypeFilter<T, ValueType>
>;

type StrictOmitByValueType<T extends object, ValueType> = Pick<
T,
StrictValueTypeFilter<T, ValueType, false>
>;

子结构的互斥处理

想象这样一个场景，假设我们有一个用于描述用户信息的对象结构，除了共有的一些基础结构以外，VIP 用户和普通用户、游客这三种类型的用户各自拥有一些独特的字段，如 vipExpires 代表 VIP 过期时间，仅属于 VIP 用户，promotionUsed 代表已领取过体验券，属于普通用户，而 refererType 代表跳转来源，属于游客。

Copy
interface VIP {
vipExpires: number;
}

interface CommonUser {
promotionUsed: boolean;
}

interface Visitor {
refererType: RefererType;
}

type User = VIP | CommonUser;

type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);

type XORUser = XOR<VIP, XOR<CommonUser, Visitor>>;

我们还可以使用互斥类型实现绑定效果，即要么同时拥有 A、B 属性，要么一个属性都没有：

Copy
type XORStruct = XOR<
{},
{
foo: string;
bar: number;
}
>;

集合工具类型

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

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

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

// 交集
type Intersection<A, B> = A extends B ? A : never;

// 差集
type Difference<A, B> = A extends B ? never : A;

// 补集
type Complement<A, B extends A> = Difference<A, B>;

type NonNullable<T> = T extends null | undefined ? never : T;

type _NonNullable<T> = Difference<T, null | undefined>

这里的具体实现其实就是条件类型的分布式特性，即当 T、U 都是联合类型（视为一个集合）时，T 的成员会依次被拿出来进行 extends U ? T1 : T2 的计算，然后将最终的结果再合并成联合类型。

我们对应地实现对象属性名的版本：

Copy
// 使用更精确的对象类型描述结构
type PlainObjectType = Record<string, any>;

// 属性名并集
type ObjectKeysConcurrence<
T extends PlainObjectType,
U extends PlainObjectType
> = keyof T | keyof U;

// 属性名交集
type ObjectKeysIntersection<
T extends PlainObjectType,
U extends PlainObjectType
> = Intersection<keyof T, keyof U>;

// 属性名差集
type ObjectKeysDifference<
T extends PlainObjectType,
U extends PlainObjectType
> = Difference<keyof T, keyof U>;

// 属性名补集
type ObjectKeysComplement<
T extends U,
U extends PlainObjectType
> = Complement<keyof T, keyof U>;

对于交集、补集、差集，我们可以直接使用属性名的集合来实现对象层面的版本：

Copy
type ObjectIntersection<
T extends PlainObjectType,
U extends PlainObjectType
> = Pick<T, ObjectKeysIntersection<T, U>>;

type ObjectDifference<
T extends PlainObjectType,
U extends PlainObjectType
> = Pick<T, ObjectKeysDifference<T, U>>;

type ObjectComplement<T extends U, U extends PlainObjectType> = Pick<
T,
ObjectKeysComplement<T, U>
>;

Copy
type Merge<
T extends PlainObjectType,
U extends PlainObjectType
// T 比 U 多的部分，加上 T 与 U 交集的部分(类型不同则以 U 优先级更高，再加上 U 比 T 多的部分即可
> = ObjectDifference<T, U> & ObjectIntersection<U, T> & ObjectDifference<U, T>;

Copy
type Assign<
T extends PlainObjectType,
U extends PlainObjectType
// T 比 U 多的部分，加上 T 与 U 交集的部分(类型不同则以 T 优先级更高，再加上 U 比 T 多的部分即可
> = ObjectDifference<T, U> & ObjectIntersection<T, U> & ObjectDifference<U, T>;

Copy
type Override<
T extends PlainObjectType,
U extends PlainObjectType
// T 比 U 多的部分，加上 T 与 U 交集的部分(类型不同则以 U 优先级更高（逆并集）)
> = ObjectDifference<T, U> & ObjectIntersection<U, T>;

模式匹配工具类型

Copy
type FunctionType = (...args: any) => any;

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

type ReturnType<T extends FunctionType> = T extends (...args: any) => infer R ? R : any;

type FirstParameter<T extends FunctionType> = T extends (
arg: infer P,
...args: any
) => any
? P
: never;

type LastParameter<T extends FunctionType> = T extends (arg: infer P) => any
? P
: T extends (...args: infer R) => any
? R extends [...any, infer Q]
? Q
: never
: never;

Copy
type ClassType = abstract new (...args: any) => any;
interface ClassType<TInstanceType = any> {
new (...args: any[]): TInstanceType;
}

type ConstructorParameters<T extends ClassType> = T extends abstract new (
...args: infer P
) => any
? P
: never;

type InstanceType<T extends ClassType> = T extends abstract new (
...args: any
) => infer R
? R
: any;

TypeScript 4.7 支持了 infer 约束功能来实现对特定类型地提取

Copy
type FirstArrayItemType<T extends any[]> = T extends [infer P extends string, ...any[]]
? P
: never;

我们在此前曾经讲到一个提取 Promise 内部值类型的工具类型 PromiseValue， TypeScript 内置工具类型中也存在这么一个作用的工具类型，并且它的实现要更为严谨：

Copy
type Awaited<T> = T extends null | undefined
? T
: T extends object & { then(onfulfilled: infer F): any }
? F extends (value: infer V, ...args: any) => any
? Awaited<V>
: never
: T;