屬性修飾工具類型#
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];
};
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]>;
};
將複雜的工具類型,拆解為由基礎工具類型、類型工具的組合 (拆分 - 處理 - 組合)
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>>>;
結構工具類型#
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#
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>
>;
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>
>;
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 代表跳轉來源,屬於訪客。
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 屬性,要麼一個屬性都沒有:
type XORStruct = XOR<
{},
{
foo: string;
bar: number;
}
>;
集合工具類型#
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
的計算,然後將最終的結果再合併成聯合類型。
我們對應地實現對象屬性名的版本:
// 使用更精確的對象類型描述結構
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>;
對於交集、補集、差集,我們可以直接使用屬性名的集合來實現對象層面的版本:
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>
>;
type Merge<
T extends PlainObjectType,
U extends PlainObjectType
// T 比 U 多的部分,加上 T 與 U 交集的部分(類型不同則以 U 優先級更高,再加上 U 比 T 多的部分即可
> = ObjectDifference<T, U> & ObjectIntersection<U, T> & ObjectDifference<U, T>;
type Assign<
T extends PlainObjectType,
U extends PlainObjectType
// T 比 U 多的部分,加上 T 與 U 交集的部分(類型不同則以 T 優先級更高,再加上 U 比 T 多的部分即可
> = ObjectDifference<T, U> & ObjectIntersection<T, U> & ObjectDifference<U, T>;
type Override<
T extends PlainObjectType,
U extends PlainObjectType
// T 比 U 多的部分,加上 T 與 U 交集的部分(類型不同則以 U 優先級更高(逆並集))
> = ObjectDifference<T, U> & ObjectIntersection<U, T>;
模式匹配工具類型#
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;
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 約束功能來實現對特定類型的提取
type FirstArrayItemType<T extends any[]> = T extends [infer P extends string, ...any[]]
? P
: never;
我們在此前曾經講到一個提取 Promise 內部值類型的工具類型 PromiseValue, TypeScript 內置工具類型中也存在這麼一個作用的工具類型,並且它的實現要更為嚴謹:
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;
References:
https://juejin.cn/book/7086408430491172901