一、提前知识说明:联合类型
typescript的联合类型是一种用于表示一个值可以是多种类型中的一种的类型。我们使用竖线(|)来分隔每个类型,所以number | string | boolean是一个可以是number,string或boolean的值的类型。
联合类型可以用于模拟一些值可能有重叠类型的情况。例如,假设我们有一个函数,它可以接受一个数字或一个字符串作为参数。我们可以使用联合类型来定义这个函数的参数类型:
function print(value: number | string) {console.log(value);}print(1); // OKprint("hello"); // OKprint(true); // Error
如果我们有一个联合类型的值,我们只能访问所有类型共有的成员。https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html?ref=hackernoon.com 例如,假设我们有一个Fish和Bird两个接口,它们都有layEggs方法,但只有Fish有swim方法,只有Bird有fly方法。我们可以定义一个返回Fish或Bird的函数:
interface Fish {swim(): void;layEggs(): void;}interface Bird {fly(): void;layEggs(): void;}declare function getSmallPet(): Fish | Bird;
然后,我们可以调用这个函数,并访问返回值的layEggs方法,因为它是Fish和Bird共有的:
let pet = getSmallPet();pet.layEggs(); // OK
但是,我们不能访问返回值的swim或fly方法,因为它们不是Fish和Bird共有的:
let pet = getSmallPet();pet.swim(); // Errorpet.fly(); // Error
要想访问这些方法,我们需要使用类型断言或者类型守卫来缩小联合类型的范围。 例如:
let pet = getSmallPet();// 使用类型断言if ((pet as Fish).swim) {(pet as Fish).swim();} else if ((pet as Bird).fly) {(pet as Bird).fly();}// 使用类型守卫function isFish(pet: Fish | Bird): pet is Fish {return (pet as Fish).swim !== undefined;}if (isFish(pet)) {pet.swim();} else {pet.fly();}
二、Exclude
typescript的Exclude是一个内置的工具类型,用于从一个联合类型中排除一些指定的类型,从而创建一个新的联合类型。https://www.typescriptlang.org/tsconfig/exclude.html 它的语法是:
Exclude
其中,Type是一个联合类型,ExcludedUnion是一个要排除的类型或者它们的联合类型,表示要从Type中排除的类型。
例如,假设我们有一个Shape类型,它定义了几种形状:
type Shape = "circle" | "square" | "triangle" | "rectangle";
如果我们想要创建一个不包含circle和square的新类型,我们可以使用Exclude来实现:
type ShapeWithoutCircleAndSquare = Exclude;
这样,ShapeWithoutCircleAndSquare类型就相当于:
type ShapeWithoutCircleAndSquare = "triangle" | "rectangle";
Exclude的实现原理是基于条件类型。条件类型可以根据一个条件表达式,选择两个可能的类型中的一个。Exclude的源码如下:
type Exclude = T extends U ? never : T;
这里,T是联合类型,U是要排除的类型。首先,T extends U检查了T是否可以赋值给U,如果可以,则返回never类型,表示排除掉T;如果不可以,则返回T本身,表示保留T。然后,这个条件类型会分布地应用到T中的每个成员上,并组合成一个新的联合类型。
三、Omit
typescript的Omit是一个内置的工具类型,用于从一个对象类型中排除一些指定的属性,从而创建一个新的对象类型。它的语法是:
Omit
其中,Type是一个对象类型,Keys是一个字符串字面量类型或者它们的联合类型,表示要从Type中排除的属性名。
例如,假设我们有一个User类型,它定义了用户的一些信息:
type User = {name: string;age: number;email: string;address: string;};
如果我们想要创建一个不包含email和address属性的新类型,我们可以使用Omit来实现:
type UserWithoutEmailAndAddress = Omit;
这样,UserWithoutEmailAndAddress类型就相当于:
type UserWithoutEmailAndAddress = {name: string;age: number;};
Omit的实现原理是基于Pick和Exclude两个工具类型。Pick用于从一个对象类型中选择一些指定的属性,Exclude用于从一个联合类型中排除一些指定的类型。Omit的源码如下:
type Omit = Pick<T, Exclude>;
这里,T是对象类型,K是要排除的属性名。首先,keyof T得到T的所有属性名组成的联合类型,然后Exclude排除掉K中指定的属性名,得到剩余的属性名组成的联合类型。最后,Pick<T, Exclude>从T中选择剩余的属性名对应的属性,得到新的对象类型。
四、pick
typescript的Pick是一个内置的工具类型,用于从一个对象类型中选择一些指定的属性,从而创建一个新的对象类型。https://www.typescriptlang.org/docs/handbook/utility-types.html 它的语法是:
Pick
其中,Type是一个对象类型,Keys是一个字符串字面量类型或者它们的联合类型,表示要从Type中选择的属性名。https://www.typescriptlang.org/docs/handbook/utility-types.html
例如,假设我们有一个Todo类型,它定义了一个待办事项的信息:
type Todo = {title: string;description: string;completed: boolean;};
如果我们想要创建一个只包含title和completed属性的新类型,我们可以使用Pick来实现:
type TodoPreview = Pick;
这样,TodoPreview类型就相当于:
type TodoPreview = {title: string;completed: boolean;};
Pick的实现原理是基于映射类型。映射类型可以根据一个已有的类型,通过遍历它的属性,生成一个新的类型。https://ultimatecourses.com/blog/using-typescript-pick-mapped-type Pick的源码如下:
type Pick = {[P in K]: T[P];};
这里,T是对象类型,K是要选择的属性名。首先,keyof T得到T的所有属性名组成的联合类型,然后K extends keyof T约束了K必须是T的属性名之一。接着,[P in K]遍历了K中的每个属性名,并将它们作为新类型的属性名。最后,T[P]得到了T中对应属性名的属性值类型,并将它们作为新类型的属性值类型。