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