类型体操剖析
常用语法元素
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, '&' | '-' | '_'>