ts内置的高级类型

203 阅读6分钟

1、提取函数类型的参数:Parameters

Parameters 用于提取函数类型的参数类型。 源码如下

type Parameters<T extends (...args: any) => any>
  = T extends (...args: infer P) => any
  ? P
  : never;
//类型参数 T 为待处理的类型,通过 extends 约束为函数,参数和返回值任意
//通过 extends 匹配一个模式类型,提取参数的类型到 infer 声明的局部变量 P 中返回

例子

type test38=Parameters<(name:string,hobby:string[])=>{}>;
//type test38 = [name: string, hobby: string[]]

2、提取函数类型的返回值类型:ReturnType

ReturnType 用于提取函数类型的返回值类型。 源码:

type ReturnType<T extends (...args: any) => any>
  = T extends (...args: any) => infer R
  ? R
  : any;

例子

type test39 = ReturnType<(name: string, hobby: string[]) => 'hello'>;
//type test39 = "hello"

3、提取构造器参数的类型:ConstructorParameters

构造器类型和函数类型的区别就是可以被 new

Parameters 用于提取函数参数的类型,而 ConstructorParameters 用于提取构造器参数的类型

源码:

type ConstructorParameters40<
  T extends abstract new (...args: any) => any
> = T extends abstract new (...args: infer P) => any
  ? P
  : never;
//通过 extends 约束为构造器类型,加个 abstract 代表不能直接被实例化(其实不加也行)
//用 T 匹配一个模式类型,提取参数的部分到 infer 声明的局部变量 P 里,返回 P

例子:

interface CatConstructor {
  new(color: string): Cat
}

type test40=ConstructorParameters<CatConstructor>;
//type test40 = [color: string]

4、提取构造器返回值的类型:InstanceType

提取了构造器参数的类型,自然也可以提取构造器返回值的类型,就是 InstanceType

源码

type InstanceType<
  T extends abstract new (...args: any) => any
> = T extends abstract new (...args: any) => infer R
  ? R
  : any;
//整体和 ConstructorParameters 差不多,只不过提取的不再是参数了,而是返回值

例子

type Cat41 = {
  catname: string;
}
interface CatConstructor41 {
  new(color: string): Cat41
}
type test41 = InstanceType<CatConstructor41>
/*
type test41 = {
    catname: string;
}
*/

5、函数可以调用 this,这个 this 的类型也可以约束:ThisParameterType

type Cat42 = {
  color: 'black'
}

//函数里可以调用 this,这个 this 的类型也可以约束:
function CatMemo(this: Cat42) {
  //console.log(this.color);
}

CatMemo.call({ color: 'black' });

源码:

type ThisParameterType42<T> =
  T extends (this: infer U, ...args: any[]) => any
  ? U
  : unknown;
//类型参数 T 为待处理的类型。
//用 T 匹配一个模式类型,提取 this 的类型到 infer 声明的局部变量 U 里返回。
//这样就实现了 this 类型的提取

6、删除 this 的类型:OmitThisParameter

源码

type OmitThisParameter43<T> =
  unknown extends ThisParameterType<T>
  ? T
  : T extends (...args: infer A) => infer R
  ? (...args: A) => R
  : T;
//用 ThisParameterType 提取 T 的 this 类型,如果提取出来的类型是 unknown 或者 any,那么 unknown extends ThisParameterType 就成立,也就是没有指定 this 的类型,所以直接返回 T//否则,就通过模式匹配提取参数和返回值的类型到 infer 声明的局部变量 A 和 R 中,用它们构造新的函数类型返回。

例子

type Cat43 = {
  color: 'black'
}

//函数里可以调用 this,这个 this 的类型也可以约束:
function CatMemo43(this: Cat42, age: number) {
  //console.log(this.color);
}

type test43 = OmitThisParameter<typeof CatMemo43>;
//type test43 = (age: number) => void

7、把索引变为可选:Partial

源码:

type Partial44<T> = {
  [P in keyof T]?: T[P];
};

例子:

type testPartial44=Partial<{race:'cat',age:1}>
/*
type testPartial44 = {
    race?: "cat" | undefined;
    age?: 1 | undefined;
}
*/

8、去掉可选:Required

源码

type Required45<T> = {
  [P in keyof T]-?: T[P];
};

例子:

type testRequired45 = Required<{ race?: 'cat', age?: 1 }>
/*
type testRequired45 = {
    race: 'cat';
    age: 1;
}
*/

9、添加readonly

源码

type Readonly46<T> = {
  readonly [P in keyof T]: T[P];
};

例子

type testReadonly46 = Readonly<{ race: 'cat', age: 1 }>
/*
type testReadonly46 = {
    readonly race: 'cat';
    readonly age: 1;
}
*/

10、Pick过滤构造新的索引类型

源码

type Pick47<T, K extends keyof T> = {
  [P in K]: T[P];
};

例子

type testPick47 = Pick<{ race: 'cat', age: 1, hobby: ['play', 'eat'] }, 'race' | 'hobby'>;
/*
type testPick47 = {
    race: 'cat';
    hobby: ['play', 'eat'];
}
*/

11、去掉这部分索引构造成新的索引类型Omit

我们知道了 Pick 可以取出索引类型的一部分索引构造成新的索引类型,那反过来就是去掉这部分索引构造成新的索引类型。

可以结合 Exclude(下面13点) 来轻松实现:

源码

type Omit51<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
//类型参数 T 为待处理的类型
//类型参数 K 为索引允许的类型(string | number | symbol 或者 string)
//通过 Pick 取出一部分索引构造成新的索引类型
//这里用 Exclude 把 K 对应的索引去掉,把剩下的索引保留。

例子

type testOmit51=Omit<{ race: 'cat', age: 1, hobby: ['play', 'eat'] }, 'race' | 'hobby'>
/*
type testOmit51 = {
    age: 1;
}
*/

12、创建索引类型:Record

源码

type Record48<K extends keyof any, T> = {
  [P in K]: T;
};
//keyof any 是动态获取的,比直接写死 string | number | symbol 更好
//它用映射类型的语法创建了新的索引类型,索引来自 K,也就是 P in K,值是传入的 T

例子

type testRecord1 = Record<'cat' | 'dog', string>;
/*
type testRecord1 = {
    cat: string;
    dog: string;
}
*/

当传入的 K 是 string | number | symbol,那么创建的就是有可索引签名的索引类型:

type testRecord2 = Record<string, number>; \
/*
type testRecord2 = {
    [x: string]: number;
}
*/

13、从联合类型中去掉一部分类型:Exclude

源码

type Exclude49<T, U> = T extends U ? never : T;
//联合类型当作为类型参数出现在条件类型左边时,会被分散成单个类型传入,这叫做分布式条件类型。
//所以写法上可以简化, T extends U 就是对每个类型的判断。

例子

type testExclude49=Exclude49<'a'|'b'|'c'|'d','a'|'b'>;
//type testExclude49 = "c" | "d"

14、从联合类型中保留一部分类型:Extract

源码

type Extract50<T, U> = T extends U ? T : never;
//可以过滤掉,自然也可以保留,Exclude 反过来就是 Extract,也就是取交集:

例子

type testExclude50 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'b'>;
//type testExclude50 = "a" | "b"

15、取Promise的ValuType的高级类型Awaited

在递归那节写过取 Promise 的 ValuType 的高级类型,这个比较常用,ts 也给内置了,就是 Awaited

源码

type Awaited52<T> =
  T extends null | undefined
  ? T
  : T extends object & { then(onfulfilled: infer F): any }
  ? F extends ((value: infer V, ...args: any) => any)
  ? Awaited<V>
  : never
  : T;
/*
类型参数 T 是待处理的类型。
如果 T 是 null 或者 undefined,就返回 T。
如果 T 是对象并且有 then 方法,那就提取 then 的参数,也就是 onfulfilled 函数的类型到 infer 声明的局部变量 F。
继续提取 onfullfilled 函数类型的第一个参数的类型,也就是 Promise 返回的值的类型到 infer 声明的局部变量 V。
递归的处理提取出来的 V,直到不再满足上面的条件。
这样就实现了取出嵌套 Promise 的值的类型的目的:
*/

例子

type testAwaited52=Awaited<Promise<Promise<Promise<number>>>>
//type testAwaited52 = number

16、判断是否为非空类型:NonNullable

NonNullable 就是用于判断是否为非空类型,也就是不是 null 或者 undefined 的类型的,实现比较简单

源码

type NonNullable<T> = T extends null | undefined ? never : T;

例子

type test1NonNullable53 = NonNullable<null>
//ype test1NonNullable53 = never

type test2NonNullable53 = NonNullable<{ race: 'cat' }>
/*
type test2NonNullable53 = {
    race: 'cat';
}
*/

17、大写、小写、首字母大写、去掉首字母大写:Uppercase、Lowercase、Capitalize、Uncapitalize

源码

type Uppercase<S extends string> = intrinsic; 
type Lowercase<S extends string> = intrinsic; 
type Capitalize<S extends string> = intrinsic; 
type Uncapitalize<S extends string> = intrinsic;

这个 intrinsic 是固有的意思,就像 js 里面的有的方法打印会显示 [native code] 一样。这部分类型不是在 ts 里实现的,而是编译过程中由 js 实现的。

其实就是 ts 编译器处理到这几个类型时就直接用 js 给算出来了。

例子:

//大写
type testUppercase54 = Uppercase<'abcd'>
//type testUppercase54 = "ABCD"

//小写
type testLowercase54 = Lowercase<'ABCD'>
//type testLowercase54 = "abcd"

//首字母大写
type testCapitalize54 = Capitalize<'abcd'>
//type testCapitalize54 = "Abcd"

//去掉首字母大写
type testUncapitalize54 = Uncapitalize<'Abcd'>
//type testUncapitalize54 = "abcd"

总结

比如用模式匹配可以实现:Parameters、ReturnType、ConstructorParameters、InstanceType、ThisParameterType。

用模式匹配 + 重新构造可以实现:OmitThisParameter

用重新构造可以实现:Partial、Required、Readonly、Pick、Record

用模式匹配 + 递归可以实现: Awaited

用联合类型在分布式条件类型的特性可以实现: Exclude

此外还有 NonNullable 和四个编译器内部实现的类型:Uppercase、Lowercase、Capitalize、Uncapitalize。

原文: juejin.cn/book/704752…