TypeScript 类型体操
- TypeScript 类型体操-语法入门篇
- TypeScript 类型体操-常见套路篇
- TypeScript 类型体操-常见套路总结
TypeScript 类型体操-常见套路篇 的简略汇总版本
常见套路总结
类型体操参见的套路总结如下
- 模式匹配做提取
- 重新构造做变换
- 递归复用做循环
- 数组长度做计数
- 联合分散可简化
- 特殊特性要记清
模式匹配做提取
TypeScript 的类型也可以通过匹配一个模式类型来提取部分类型到 infer 声明的局部变量中返回。
ts
// 提取 Promise 的返回值类型
type GetValueType<P> = P extends Promise<infer Value> ? Value : never;
// 数组
// 提取数组的第一个元素
type GetFirst<Arr extends unknown[]> = Arr extends [infer First, ...unknown[]] ? First : never;
// 字符串
// 字符串替换
type ReplaceStr<Str extends string, From extends string, To extends string> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${Suffix}`
: Str;
// 去掉字符串右边的空白字符
type TrimStrRight<Str extends string> = Str extends `${infer Rest}${' ' | '\n' | '\t'}` ? TrimStrRight<Rest> : Str;
// 函数
// 提取函数的参数类型和返回类型
type GetParameters<Func extends Function> = Func extends (...args: infer Args) => unknown ? Args : never;
type GetReturnType<Func extends Function> = Func extends (...args: any[]) => infer ReturnType ? ReturnType : never;
//对象
// 提取 ref 属性的类型
type GetRefProps<Props> = 'ref' extends keyof Props ? (Props extends { ref?: infer Value | undefined } ? Value : never) : never;
重新构造做变换
TypeScript 类型系统可以通过 type 声明类型变量,通过 infer 声明局部变量,类型参数在类型编程中也相当于局部变量,但是它们都不能做修改,想要对类型做变换只能构造一个新的类型,在构造的过程中做过滤和转换。
ts
// 数组
// 添加元素
type Push<Arr extends unknown[], Ele> = [...Arr, Ele];
// 递归zip
type Zip<Arr1 extends unknown[], Arr2 extends unknown[]> = Arr1 extends [infer Arr1_1, ...infer Arr1_Other]
? Arr2 extends [infer Arr2_1, ...infer Arr2_Other]
? [[Arr1_1, Arr2_1], ...Zip<Arr1_Other, Arr2_Other>]
: []
: [];
// 字符串
// 大写首字母
type CapitalizeStr<Str extends string> = Str extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : Str;
// 下划线转小驼峰
type CamelCase<Str extends string> = Str extends `${infer Left}_${infer Right}${infer Rest}` ? `${Left}${Uppercase<Right>}${CamelCase<Rest>}` : Str;
// 函数
// 添加参数
type AppendArgument<Func extends Function, Arg> = Func extends (...args: infer Args) => infer ReturnType ? (...args: [...Args, Arg]) => ReturnType : never;
// 对象
// 属性去除可选和readonly
type Mapping<Obj extends object> = {
-readonly [k in keyof Obj]-?: Obj[k];
};
// 提取指定值类型的对象
type Mapping<Obj extends Record<string, any>, ValueType> = {
[K in keyof Obj as Obj[K] extends ValueType ? K : never]: Obj[K];
};
递归复用做循环
在 TypeScript 类型编程中,遇到数量不确定问题时,就要条件反射的想到递归,每次只处理一个类型,剩下的放到下次递归,直到满足结束条件,就处理完了所有的类型。
ts
type IsEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
// 不确定层数的 Promise 提取类型
type DeepPromiseValueType<T> = T extends Promise<infer Value> ? DeepPromiseValueType<Value> : T;
// 反转数组
type ReverseArr<Arr extends unknown[]> = Arr extends [...infer Left, infer Value] ? [Value, ...ReverseArr<Left>] : [];
// 包含
type Includes<Arr extends unknown[], Item> = Arr extends [...infer Left, infer Value]
? IsEqual<Value, Item> extends true
? true
: Includes<Left, Item>
: false;
// 删除指定元素
type RemoveItem<Arr extends unknown[], Item> = Arr extends [...infer Left, infer Value]
? IsEqual<Value, Item> extends true
? [...Left]
: [...RemoveItem<Left, Item>, Value]
: [];
// 构造数组
type BuildArray<Len extends number, Ele = unknown, Arr extends unknown[] = []> = IsEqual<Arr['length'], Len> extends false
? BuildArray<Len, Ele, [...Arr, Ele]>
: Arr;
// ReplaceAll
type ReplaceAll<Str extends string, From extends string, To extends string> = Str extends `${infer Prefix}${From}${infer Suffix}`
? `${Prefix}${To}${ReplaceAll<Suffix, From, To>}`
: Str;
// 字符串转联合类型
type StringToUnion<Str extends string> = Str extends `${infer First}${infer Res}` ? First | StringToUnion<Res> : never;
// 反转字符串
type ReverseStr<Str extends string, Res extends string = ''> = Str extends `${infer First}${infer Last}` ? `${ReverseStr<Last, `${First}${Res}`>}` : Res;
// 递归对象,没有计算的key 就不会继续递归 Obj extends never 或者 Obj extends any 等 触发计算
type DeepReadonly2<Obj extends Record<string, any>> = {
readonly [k in keyof Obj]: Obj[k] extends any ? (Obj[k] extends Object ? (Obj[k] extends Function ? Obj[k] : DeepReadonly<Obj[k]>) : Obj[k]) : never;
};
数组长度做计数
TypeScript 类型系统没有加减乘除运算符,但是可以构造不同的数组再取 length 来得到相应的结果。这样就把数值运算转为了数组类型的构造和提取。
ts
type IsEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
// 核心构造数组
type BuildArray<Len extends number, Ele = unknown, Arr extends unknown[] = []> = IsEqual<Arr['length'], Len> extends false
? BuildArray<Len, Ele, [...Arr, Ele]>
: Arr;
// 加
type Add<A extends number, B extends number> = [...BuildArray<A>, ...BuildArray<B>]['length'];
// 减
type Subtract<A extends number, B extends number> = BuildArray<A> extends [...BuildArray<B>, ...infer Right] ? Right['length'] : never;
// 乘
type Multiply<A extends number, B extends number, Res extends unknown[] = []> = B extends 0
? Res['length']
: Multiply<A, Subtract<B, 1>, [...Res, ...BuildArray<A>]>;
// 除
type Divide<A extends number, B extends number, Res extends unknown[] = []> = A extends 0 ? Res['length'] : Divide<Subtract<A, B>, B, [...Res, unknown]>;
// 字符串长度
type StrLen<Str extends string, Res extends unknown[] = []> = Str extends `${infer First}${infer Other}` ? StrLen<Other, [...Res, unknown]> : Res['length'];
// 比较大小,构造一个数组,看数组长度先到达的那个数
type GreaterThan<Num1 extends number, Num2 extends number, Res extends unknown[] = []> = Res['length'] extends Num1
? false
: Res['length'] extends Num2
? true
: GreaterThan<Num1, Num2, [...Res, unknown]>;
联合分散可简化
TypeScript 对联合类型做了特殊处理,当遇到字符串类型或者作为类型参数出现在条件类型左边的时候,会分散成单个的类型传入做计算,最后把计算结果合并为联合类型。
ts
// [][number] 转联合
type BEM<block extends string, element extends string[], modifier extends string[]> = `${block}__${element[number]}--${modifier[number]}`;
// 全排列
type Combination<A extends string, B extends string> = A | B | `${A}${B}` | `${B}${A}`;
type AllCombinations<A extends string, B extends string = A> = A extends A ? Combination<A, Exclude<B, A>> : never;
特殊特性要记清
ts
// any 类型与任何类型的交叉都是 any,也就是 1 & any 结果是 any。
type IsAny<T> = 2 extends 1 & T ? true : false;
// 泛型函数类型 只有参数相等的时候才可以 extends
type IsEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
// 联合类型的分布计算特性
type IsUnion<A, B = A> = A extends A ? ([B] extends [A] ? false : true) : never;
// never 在条件类型中的返回值永远是 never
type IsNever<T> = [T] extends [never] ? true : false;
// any 在条件类型中的返回值是 trueType 和 falseType 的联合类型
type TestAny<T> = T extends number ? 1 : 2; // 1 | 2
// 元组类型的 length 是数字字面量,而数组的 length 是 number。
type IsTuple<T> = T extends [...params: infer Eles] ? NotEqual<Eles['length'], number> : false;
// 函数的参数具有逆变的特性
type UnionToIntersection<U> = (U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown ? R : never;
// {} 可以 extends 可选对象
type GetOptional<Obj extends Record<string, any>> = {
[Key in keyof Obj as {} extends Pick<Obj, Key> ? Key : never]: Obj[Key];
};
// 可索引签名不能构造成字符串字面量类型
type RemoveIndexSignature<Obj extends Record<string, any>> = {
[Key in keyof Obj as Key extends `${infer Str}` ? Str : never]: Obj[Key];
};
练手
ts
type ParseQueryString<Str extends string> = '';
type res1 = ParseQueryString<'a=1'>; // {a: '1'}
type res2 = ParseQueryString<'a=1&b=2&c=3'>; // {a: '1', b: '2', c: '3'}
type res3 = ParseQueryString<'a=1&a=2&a=3&b=2&c=3'>; // {a: ['1', '2', '3'], b: '2', c: '3'}
type res4 = ParseQueryString<'a=1&a=2&a=1&b=2&c=3&a=1&a=2'>; // {a: ['1', '2'], b: '2', c: '3'}
大体逻辑就是递归, 先模式匹配出 &
分割的字符串,在模式匹配出=
风格的 key value
, 最后合并对象就可以了
ts
type IsEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
type Includes<Arr extends unknown[], Item> = Arr extends [infer First, ...infer Last]
? IsEqual<First, Item> extends true
? true
: Includes<Last, Item>
: false;
type ParseQueryString<Str extends string> = Str extends `${infer Left}&${infer Right}`
? MergeParams<ParseParam<Left>, ParseQueryString<Right>>
: ParseParam<Str>;
type ParseParam<Str extends string> = Str extends `${infer Key}=${infer Value}` ? { [k in Key]: Value } : {};
type MergeParams<Obj1 extends Record<string, any>, Obj2 extends Record<string, any>> = {
[Key in keyof Obj1 | keyof Obj2]: Key extends keyof Obj1
? Key extends keyof Obj2
? MergeValue<Obj1[Key], Obj2[Key]>
: Obj1[Key]
: Key extends keyof Obj2
? Obj2[Key]
: never;
};
type MergeValue<Cur, Pre> = Cur extends Pre ? Cur : Cur extends unknown[] ? MergeValue2<Pre, Cur> : Pre extends unknown[] ? MergeValue2<Cur, Pre> : [Cur, Pre];
type MergeValue2<Cur, Pre extends unknown[]> = Includes<Pre, Cur> extends true ? Pre : [Cur, ...Pre];
type res1 = ParseQueryString<'a=1'>; // {a: '1'}
type res2 = ParseQueryString<'a=1&b=2&c=3'>; // {a: '1', b: '2', c: '3'}
type res3 = ParseQueryString<'a=1&a=2&a=3&b=2&c=3'>; // {a: ['1', '2', '3'], b: '2', c: '3'}
type res4 = ParseQueryString<'a=1&a=2&a=1&b=2&c=3&a=1&a=2'>; // {a: ['1', '2'], b: '2', c: '3'}
export {};