ts类型体操傻瓜式剖析

208 阅读3分钟

类型体操剖析

常用语法元素

infer

条件: 配合extends 使用,反向推导

… 拓展符

条件 : 处于集合,数组列表的展开,模版字符的展开

Array[’length’]

常用于加减乘除运算,用于中间 具有 数量概念的元素到 number 类型的转换,十分强大

模版字符类型Template Literal Types

用于字符的拆解跟推导

范型

常常搭配其他元素使用

应该要有的思维模式

以递归迭代的方式去思考,拆解子问题的方式

十分抽象,如何真的去实现一个呢

前提条件:得充分了解ts的语法规则,跟类型系统。这些可以在www.typescriptlang.org/docs/handbo…去了解。

自己类比,举一反三

语法元素详细介绍

infer

思维模式,套模版的思维

将 需要拆解的类型放在左边,extends 右边 反映你需要拆解的模版,将你需要的类型 使用infer 提取出来

案例1:将数组的元素类型提取出来

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

分析:

拆解的类型 Type

extends

提取 Array<元素>

该元素 使用 infer 和一个无中生有的标识符 Item

返回 元素类型

案例2:将函数的返回值类型提取出来

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return
  ? Return
  : never;

分析:

拆解的类型 Type

extends

提取模版 (...args: never[]) => 返回值

所以需要提取【返回值】

将返回值替换 成 infer 加一个莫须有的标识符 Return

案例3: 思考,如何提取 Promise 的返回类型?

const promiseA = new Promise<number>((resolve, reject) => {
	resolve(1)
})

type B = GetPromiseResolveType<typeof promiseA> // expect B === number

案例4: 思考,如何提取函数第一个参数的类型?

function A(name: string){}
type AA = GetFuncParamType<typeof A> // string

案例5: 获取一个多级数组的所有元素类型?

// 获取扁平数组里面的元素类型
type GetArrayFlatItemTypes<T> = T extends Array<infer A> ? GetArrayFlatItemTypes<A> : T;
type GetArrayFlatItemTypesA<T> = T extends (infer Head)[] ? GetArrayFlatItemTypesA<Head> : T;

const bb = [1, 2, 3, [1, 2, 3, true, [{ a: 1, b: 2 }]], null]

type BB = GetArrayFlatItemTypes<typeof bb>
type CC = GetArrayFlatItemTypesA<typeof bb>
// number | boolean | {
//    a: number;
//    b: number;
//} | null

… 拓展符

思维模式

提取列表类型的感觉

一般情况配合infer 使用

案例一:

type GetLastOne<T> = T extends [...infer Head, infer Last] ? Last : never;
type GetFirstOne<T> = T extends [infer Head, ...infer Last] ? Head : never;

const A: [number, boolean, null] = [0,true,null];

type B = GetFirstOne<typeof A>; // number
type C = GetLastOne<typeof A>; // null

案例二:

function aa(...args:[string, boolean]){
    return 1
}

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

type AALastParamType = GetLastParam<typeof aa> // boolean

Array.Length

案例一:

const A: [number, boolean, null] = [0, true, null];
type Length<T> = T extends Array<any> ? T['length'] : never;
const lengthOfbb: Length<[string, boolean, ...typeof A]> = 5;

案例二:

记得前面提到

用于中间 具有 数量概念的元素到 number 类型的转换

实现加法

思路:

利用数字构造出数组类型。取length字段

然后使用拓展符合并两个数组,取length字段

过程:

数字—-数组—-合并——数字

type BuildArray<
    Length extends number, 
    Ele = unknown, 
    Arr extends unknown[] = []
> = Arr['length'] extends Length 
        ? Arr 
        : BuildArray<Length, Ele, [...Arr, Ele]>;
type Add<Num1 extends number, Num2 extends number> = 
    [...BuildArray<Num1>,...BuildArray<Num2>]['length'];

const num: Add<3, 5> = 8; // 8 如果赋值其他数字会报错

案例三:

模式: Subtract<Num1, Num2> === > Num1 - Num2

实现减法

核心思路

递推模版:

模版占位

Num1 = Num2 + Rest

移项 Rest = Num1 - Num2. === > Subtract<Num1, Num2>

所以有了一下关系

BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest]
// 
type Subtract<Num1 extends number, Num2 extends number> = 
    BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest]
        ? Rest['length']
        : never;
const num: Subtract<10, 5> = 5; // 5 如果赋值其他数字会报错

思考: 如何计算字符串长度?

思考:乘法如何实现?除法呢?

分解的思想

减法

模版字符类型Template Literal Types

案例一:首字母必须是大写

规范格式

type HeadUpper<T> = T extends `${infer Head}${infer Tail}` ? Head : never;

type A = HeadUpper<'stssd'> // 's'

案例二:

type Placement = Side
  | "left-start" | "left-end" 
  | "right-start" | "right-end" 
  | "top-start" | "top-end" 
  | "bottom-start" | "bottom-end"

改写

type Alignment = 'start' | 'end';
type Side = 'top' | 'right' | 'bottom' | 'left';
type AlignedPlacement = `${Side}-${Alignment}`;
type Placement = Side | AlignedPlacement;

案例三:强制转换

type ToString<T extends string | number | boolean | bigint> = `${T}`;

案例四:排列组合

type T4 = Concat<"top" | "bottom", "left" | "right">;
// "top-left" | "top-right" | "bottom-left" | "bottom-right"

[${A|B|C}]` => `[${A}]` | `[${B}]` | `[${C}]`

`${A|B}-${C|D}` => `${A}-${C}` | `${A}-${D}` 
  | `${B}-${C}` | `${B}-${D}`

案例五:键值转换

type Getters<T> = {
	[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person {
	name: string;
	age: number;
	location: string;
}

Getters<Person> // getName: () => string

业务实践介绍

工具类型

Capitalize

www.typescriptlang.org/docs/handbo…

下划线转驼峰

type CamelCase<S> = S extends `${infer R}${infer K}`
  ? R extends '_' | '-' | ' '
    ? K extends ''
      ? R
      : `${CamelCase<Capitalize<K>>}`
    : `${R}${CamelCase<K>}`
  : S

type Camelize<T extends Record<string, any>> = {
  [R in keyof T as CamelCase<R>]: T[R]
}

进阶推广,按照特定字符分割识别转换

type CamelCase<S, U extends string> = S extends `${infer R}${infer K}`
  ? R extends U
    ? K extends ''
      ? R
      : `${CamelCase<Capitalize<K>, U>}`
    : `${R}${CamelCase<K, U>}`
  : S

type Camelize<T extends Record<string, any>, U extends string > = {
  [R in keyof T as CamelCase<R, U>]: T[R]
}

interface AA {
    'get&name': string,
    'get-age': string,
    'get_college': string,
}

type AAA = Camelize<AA, '&' | '-' | '_'>

JSX

www.typescriptlang.org/docs/handbo…