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 {};
相关推荐
既见君子6 小时前
TypeScript 类型体操-常见套路篇
javascript·typescript
m0_748251522 天前
Axios结合Typescript 二次封装完整详细场景使用案例
前端·javascript·typescript
Random_index2 天前
#Ts篇: Record<string, number> 是 TypeScript 中的一种类型定义,它表示一个键值对集合
typescript
曼陀罗3 天前
import 一个js文件,报ts类型错误的解决思路
前端·typescript
RogerLHJ4 天前
cocos creator 的 widget组件的使用及踩坑
typescript·游戏引擎·游戏程序·cocos2d
Java陈序员4 天前
一个开源免费中后台模版!
vue.js·typescript·vite
winfield8214 天前
TypeScript 编程,|| 和 ?? 的区别是什么?
前端·typescript
zzb15804 天前
Exp 智能协同管理系统-部门管理前端页面开发
前端·typescript·前端框架·vue
刺客-Andy5 天前
React第十五节useReducer使用详解差异
前端·javascript·react.js·typescript·前端框架