TS常用内置工具类型与实现

135 阅读7分钟

基于条件类型

前置

在了解基于条件类型的内置类型前,需要了解条件类型的相关知识

条件类型

对于条件类型,官方的定义是

T extends U ? X : Y

上面的类型意思是,若T能够赋值给U,那么类型是X,否则为Y

从上面的描述可以看出,条件类型看起来有点像 JavaScript 中的三元表达式(T extends U ?真 X : 假 Y )

也就是判断extends左边是否是右边的子类型,如果子类型 extends 父类型 = true,否则false

子类型: never -> 字面量类型 -> 基础类型 -> 包装类型 -> any / unknown

type T1 = never extends "str" ? true : false;   // true
type T2 = "str" extends string ? true : false;  // true
type T3 = string extends String ? true : false; //  true

限制:条件类型只能在type中使用

分发机制

条件类型有分发机制,产生分发机制的条件

1.联合类型通过泛型传递

2.类型需要是裸类型 (泛型没有和别人搭配)

简单的例子如下:

type Temp<T> = T extends string ? 1 : 2;
​
type Temp = Temp<string | number>;  // 1 | 2

1.联合类型string | number通过泛型传递

2.extends左边泛型T是裸类型(如果左边是T & {}或者[T]等就不是裸类型)

推导过程:

1.满足分发机制的条件

2.把extends左边的T也就是联合类型string | number的每个类型和extends右边的类型进行比较,相当于

string extends string ? 1 : 2; // 1

number extends string ? 1 : 2; // 2

3.分发比较的结果会组成联合类型1|2返回

Extract

提取T中可以赋值给U的类型

// Extract<T, U>    
type T = Extract<1 | 2 | 3 | 4, 1 | 2 | 5>; // 1 | 2

官方实现

type Extract<T, U> = T extends U ? T : never;

推导过程:

1、联合类型1 | 2 | 3 | 4通过泛型传递,extends左边泛型T是裸类型,满足分发机制

2、把extends左边的T也就是联合类型`1 | 2 | 3 | 4的每个类型和extends右边的类型1 | 2 | 5进行比较,相当于

1 extends 1 | 2 | 5 ? 1 : never; // 1
2 extends 1 | 2 | 5 ? 2 : never; // 2
3 extends 1 | 2 | 5 ? 3 : never; // never
4 extends 1 | 2 | 5 ? 4 : never; // never

3、分发比较的结果会组成联合类型1|2返回

Exclude

T中剔除可以赋值给U的类型

// Exclude<T, U>
type T = Exclude<1 | 2 | 3 | 4, 1 | 2>;  // 3 | 4

官方实现

type Exclude<T, U> = T extends U ? never : T;

这里的推导过程与Extract类似,只是类型T能赋给U时,返回的是never,不能赋给U返回的是T,达到剔除U的效果

NonNullable

T中剔除nullundefined

// NonNullable<T>
type T = NonNullable<string | number | undefined>;  // string | number

官方实现

// 旧版的实现
type NonNullable<T> = T extends null | undefined ? never : T;
// 新版的实现
type NonNullable<T> = T & {};

旧版的实现利用的是条件类型分发机制,类似Exclude的效果将nullundefined排除

新版的实现利用的是&交叉类型

  • 类型T与空对象类型 {}相交,结果只包含 T 类型的属性和方法
  • 交叉类型不允许存在 nullundefined 类型

所以排除nullundefined

基于映射类型

前置

在了解基于映射类型的内置类型前,需要了解相关知识

keyof

keyof关键字可以获取对象中的类型作为联合类型

type T1 = {
    name: string;
    age: number;
}
type T2 = keyof T1 // 'name' | 'age'

映射类型

映射类型的语法

{ [ K in T ] : U }

1、映射类型类似for...inT是一个联合类型,然后对联合类型进行遍历;K是则是联合类型T的每一项,U是每一项对应的类型

2、映射类型相当于创建一个新的对象类型,左边的[ K in T ]作为对象类型的所有属性名,右边的U是对应属性名的属性值

3、映射类型只能在type中使用

type T1 = 'a' | 'b';
type T2 = {
    [ K in T1 ] : string;
}
// type T2 = {
//   a: string;
//   b: string;
// }

映射类型常与keyof一起配合使用,将一个对象类型变成一个新对象类型

type T1 = {
    name: string;
    age: number;
}
type T2 = {
    [ K in keyof T1 ] : any;
}
// type T2 = {
//   name: any;
//   age: any;
// }

Partial

指定类型T的所有属性都变为可选

// Partial<T>type T1 = {
    name: string;
    age: number;
}
type T2 = Partial<T1>
// type T2 = {
//    name?: string
//    age?: number
//}

官方实现

type Partial<T> = {
  [key in keyof T]?: T[key];
};

利用映射类型,遍历类型,在属性名后面加?变成可选属性

Required

指定类型T的所有属性都变为必选

// Required<T>type T1 = {
    name?: string;
    age?: number;
}
type T2 = Required<T1>
// type T2 = {
//    name: string
//    age: number
//}

官方实现

type Required<T> = {
  [key in keyof T]-?: T[key];
};

遍历对象类型,在属性名后面加-?变成必选属性

Readonly

指定类型 T 的所以属性都变成只读

// Readonly<T>type T1 = {
    name: string;
    age?: number;
}
type T2 = Readonly<T1>
// type T2 = {
//   readonly name: string
//   readonly age: number
//}

官方实现

type Readonly<T> = {
  +readonly [K in keyof T]: T[K];
};

遍历对象类型,在属性名前面加readonly变成只读属性

Pick

从类型 T中选择指定的属性 K,返回一个新的类型,只包含选定的属性

// Pick<T, K extends keyof T> 
type T1 = {
    name: string
    age: number
}
type T2 = Pick<T1, 'age'>
// type T2 = { age: number }

官方实现

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

这里的K extends keyof T中的extends是泛型约束,表示泛型K只能传递与keyof T相关的字面量类型。

推导过程:

1、遍历选定的联合类型KK中的任意一项key作为新类型的属性名

2、从类型T中找到属性名为key对应的属性值,赋给对应的属性名,组成一个新类型

Omit

从类型T 中排除指定的属性 K,返回一个新的类型,不包含指定的属性

// Omit<T, K extends keyof T>
type T1 = {
    name: string
    age: number
}
type T2 = Omit<T1, 'age'>
// type T2 = { name: string }

官方实现

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Omit利用了之前学习的PickExclude工具类型,将类型T需要排除的属性K改为获取类型T指定的属性,这个属性是类型T排除了K中的属性之后的属性

Record

// Record<T, R>
type T = Record<string, any>
const obj: T = {
    name: string
    age: number
}
​
// 使用 Record 创建了一个名为 data 的对象,它的属性名是字符串类型,属性值是 Person 类型的对象。
// 这样我们就可以通过属性名来访问对应的 Person 对象。
type Person = {
  name: string;
  age: number;
};
​
const data: Record<string, Person> = {
  john: { name: "John", age: 25 },
  jane: { name: "Jane", age: 30 },
};

官方实现

type Record<T extends keyof any, K> = {
  [key in T]: K;
};

接受两个类型参数:TKT是一个联合类型,表示要创建的对象的属性名称的集合,而 K则表示属性的类型。不会限制对象的属性数量,它只是指定了属性的名称和类型

基于推断类型

前置

inter

infer只能在条件类型中使用,提取待推断的类型变量。infer放在不同的位置,可以帮我们区不同位置的类型

inter示例如下:

// 获取函数类型的返回值
type Func = (a: string, b: number) => { name:string; age:number; }
type ReturnType<T> = T extends (...args: any) => infer R ? R : any;
​
type T1 = ReturnType<Func>
// type T1 = {
//    name: string;
//    age: number;
// }

推导过程:

1、待推断的类型infer R的位置放在(...args: any) => infer R函数的返回值的地方,所以这里提取的就是函数类型的返回值

2、最后ReturnType的结果返回的R就是推导出来的函数类型的返回值

另一个示例如下:

// 获取参数类型
type Func = (a: string, b: number) => { name:string; age:number; }
type Parameters<T> = T extends (
  ...args: infer R
) => any
  ? R
  : false;
​
type T1 = Parameters<Func>
// type T1 = [a: string, b: number]

推导过程:

1、infer R的位置放在...args类型的地方,所以这里提取的就是参数的类型

ReturnType

获取函数返回值类型

官方实现

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

在上面inter获取函数返回值的例子里加上泛型约束

Parameters

返回函数函数参数类型元组

官方实现

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

在上面inter获取函数参数的例子里加上泛型约束