针对基础的Ts知识不做过多解释,相关文档查阅即可.本文注重对于类型操作中的难点和日常项目中的高频类型等做整理.本文会在内置的高级类型基础上延展.
针对内置的高级类型,这里按照操作类型做了简单的分类.

1.函数类

1.Parmeters<T>

/**
* 返回函数的参数类型
*/
type Parameters<T extends (...args: any) => any> = T extends (
...args: infer P
) => any
? P
: never;

//e.g.
type FunctionParamsType = Parameters<(arg1: string, arg2: number) => {}>;
//expected to be [arg1: string, arg2: number]

2.ReturnType<T>

 /**
* 返回函数的返回类型
*/
type ReturnType<T extends (...args: any) => any> = T extends (
...args: any
) => infer R
? R
: never;

//e.g.
type FunctionReturnType = ReturnType<() => 'string'>;
// expected to be string

以上两个高级类型中有三个知识点

1. 类型约束 extends

入参部分的T extends (...args: any) => any用来约束入参的类型为函数类型

2. 条件:extends ? :

类似JS中的三元运算,比如T extends number?true:false经过计算返回布尔类型,如果T的类型是number则返回true,否则false

3.infer 推导

当在类型计算过程中需要提取其中的类型时,我们可以再适当的位置加 infer来声明部分类型


2.元组类

1.Exclude<T,U>

/**
* 从联合类型中排除一部分类型,构造类型
*/
type Exclude<T, U> = T extends U ? never : T;
//e.g.
type UnionExclude = Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'b'>;
//expected to be 'c'|'d'

2.Extract<T,U>

/**
* 从两个联合类型中取交集,构造类型
*/
type Extract<T, U> = T extends U ? T : never;
//e.g.
type UnionExtract = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'b'>;
// expected to be 'a'|'b'

这其中有个重要的知识点就是分布式条件类型,即类型参数为联合类型时,并且在条件类型左边直接引用该类型参数的时候,TypeScript 会把每一个元素单独传入来做类型运算,最后再合并成联合类型.

分布式条件类型条件:

1. 入参为联合类型

2. extends左边直接对联合类型进行引用 A extends A 是分布式条件类型 ;[A] extends [A] 不是是分布式条件类型

所以再回头分析Exclude<'a' | 'b' | 'c' | 'd', 'a' | 'b'>,T extends U右边类型保持不变,将'a' | 'b' | 'c' | 'd'拆开依次传入,第一次结果'a' extends 'a'|'b',结果为never;第二次'b' extends 'a'|'b',结果为never;第三次'c' extends 'a'|'b',结果为'c';第四次'd' extends 'a'|'b',结果为'd',最后将结果在重新组合成联合类型即'c'|'d';


3.索引类型

1.Partial<T>

/**
* 构造一个所有属性都可选的类型
*/
type Partial<T> = {
[key in keyof T]?: T[key];
};
//e.g.
type IndexTypePartial = Partial<{ a: number; b: string }>;
//expected to be {a?: number | undefined;b?: string | undefined;}

2.Required<T>

/**
* 构造一个所有属性都必须得类型
*/
type Required<T> = {
[key in keyof T]-?: T[key];
};
//e.g.
type IndexTypeRequired = Required<{ a?: number; b: string }>;
//expected to be {a: number;b: string }

3.Readonly<T>

/**
* 构造一个所有属性都是只读的类型
*/
type Readonly<T> = {
readonly [key in keyof T]: T[key];
};
//e.g.
type IndexTypeReadonly = Readonly<{ a: number; b: string }>;
//expected to be { readonly a: number;readonly b: string }

以上三个类型都用到一个知识点是索引类型的重新构造,索引类型有readonly(只读),?(可选)两种修饰符,这个过程中可以对Value值Key值进行修改,Value值修改直接写入新的类型即可,Key值的修改要用的as,即重映射

下面的例子将Value值类型全部改写为boolean

type Mapping<Obj extends object> = { 
[Key in keyof Obj]: boolean
}

type Res=Mapping<{a:number,b:string}>
//expected to be { a: boolean; b: boolean }

下面的例子是将索引类型的Key大写

因为索引可能是string、number、symbol,而Uppercase只能接受string所以通过交叉类型来限制入参

type UppercaseKey<Obj extends object> = { 
[Key in keyof Obj as Uppercase<Key & string>]: Obj[Key]
}

上面三种内置的高级类型具有代表性,其余的比如构造函数和Promise相关的类型可以参考官网文档,因为不具有代表性,不做过得解释.额外添加了几个自定义高级类型,来总结其余的知识点

1.递归循环与模板字面量

CamelCaes<T>,将连字符字符串转驼峰,比如start_time=>startTime,

type CamelCaes<S extends string> = S extends `${infer First}_${infer Rest}`
? `${First}${CamelCaes<Capitalize<Rest>>}`
: S;

上面的自定义类型用到了两个知识点:模板字面量递归循环

模板字面量,类似JS中的语法一样,在合适的位置通过infer声明变量,再通过条件类型提取出来.

下面的例子,我们通过_下划线来构建,左边部分和右边部分,下划线左边部分保持不变,下划线右边部分调用内置类型Capitalize将首字母大写;但是仅仅这样,如果超过三个以上的单词则无法满足;

 type CamelCaes<S extends string> = S extends `${infer First}_${infer Rest}`
? `${First}${Capitalize<Rest>}`
: S;
type Res = CamelCaes<'activity_product_detail'>;
//expected to be 'activityProduct_detail'

所以对于右边的剩余部分,我们就要采用递归的方法,继续调用;${First}${CamelCaes<Capitalize<Rest>>}


2.模板字面量与联合类型

字符串类型中遇到联合类型的时候,会每个元素单独传入计算,无需再进行递归循环,最典型的例子就是BEM规范

type BEM<
B extends string,
E extends string[],
M extends string[]
> = `${B}__${E[number]}--${M[number]}`;

type res = BEM<'btn', ['price'], ['success', 'error']>;
//expected to be "btn__price--success" | "btn__price--error"


3.any类型的交叉类型

any 类型与任何类型的交叉都是 any,所以可以用这个特性来判断一个类型是否是any

type IsAny<T> = number extends (number & T) ? true : false


4.any类型作为参数

any 在条件类型中也比较特殊,如果类型参数为 any,会直接返回 trueType 和 falseType 的合并

type TestAny<T> = T extends number ? 1 : 2;

type Res=TestAny<any>
//expected to be 1|2


5.元祖类型的length与数组length

元组类型的 length 是数字字面量,而数组的 length 是 number。

type len=[1,2,3]['length]
//expected to be 3

type len2=number[]['length]
//expected to number


6.可选索引的索引可能没有,Pick 出来的就可能是 {}

type res = {} extends Pick<{ a?: 1 }, 'a'> ? true : false;
//expected to be true

通过这个特点可以自定义一个类型用来过滤可选索引

type GetOptional<Obj extends  Record<string, any>> = {
[
Key in keyof Obj
as {} extends Pick<Obj, Key> ? Key : never
] : Obj[Key];
}

反过来也过滤非可选索引

7.数值计算

这一部分在TS中算是比较难得知识点,核心用法就是利用数组的长度属性来出来.

type res=[1,2,3]['length']  //expected to 3
type res=['a','b','c']['length'] //expected to 3

举例求两个数的和

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'];


//e.g.
type res = Add<4, 5>;expected to 9

其余的不过多解释,需要了解的请自行搜索



思考:过程中发现了这样一个问题,目前还在查阅资料;没想到合理的解释