Skip to content

Latest commit

 

History

History
297 lines (216 loc) · 7.36 KB

05 - 高级类型.md

File metadata and controls

297 lines (216 loc) · 7.36 KB

05 - 高级类型

相交类型

我们可以使用 & 把两个自定义类型相交得到一个新的类型。例如:

type Admin = {
  name: string;
  privileges: string[];
};

type Employee = {
  name: string;
  startDate: Date;
};

type ElevatedEmployee = Admin & Employee;

const e1: ElevatedEmployee = {
  name: "Lebron",
  privileges: ["create-server"],
  startDate: new Date()
};

两个类型相交后,得到的是

同样地,也适用于多个 Interfaces 的相交。

类型检查

基本数据类型

如果想判断一个变量是否属于基本数据类型,可以使用 typeof。例如:

let a: any;
a = "I'm a string";
if (typeof a === "string") {
  console.log(a);
}

自定义类型

如果想访问自定义类型变量的属性或者函数,如何判断?这里介绍两种方法。

1. "property" in variable

我们先来看下面例子:

type UnknownEmployee = Employee | Admin;

function printEmployeeInfo(emp: UnknownEmployee) {
  console.log(`Name: ${emp.name}`);
  if ("privileges" in emp) {
    console.log(`Privileges: ${emp.privileges}`);
  }
  if ("startDate" in emp) {
    console.log(`Privileges: ${emp.startDate}`);
  }
}

1)首先定义了一个 UnknownEmployee,它是 EmployeeAdmin 的联合类型。 2)在 printEmployeeInfo 方法实现中,因为 EmployeeAdmin 都有定义 name 属性,所以可以直接访问 emp.name。 3)privilegesstartDate 不是 EmployeeAdmin 都有的属性,所以需要进行判断才能访问对应的属性。这里可以使用类似 "privileges" in emp 去判断一个变量中是否有这个属性。

2. 字面类型属性

我们先来看下面例子:

interface Bird {
  type: "bird";
  flyingSpeed: Number;
}

interface Horse {
  type: "horse";
  runningSpeed: Number;
}

type Animal = Bird | Horse;

function moveAnimal(animal: Animal) {
  let speed;
  switch (animal.type) {
    case "bird":
      speed = animal.flyingSpeed;
      break;
    case "horse":
      speed = animal.runningSpeed;
      break;
  }
  console.log(`Moving at speed: ${speed}`);
}

例子中,为了区别 animalBird 还是 Horse,在接口定义时添加了字面类型 type。这样就可以在访问 animal 的属性时,使用 type 去区分它们。

如果想判断一个变量是否是某个类的实例,可以使用 instanceof。例如:

class Car {
  drive() {
    console.log("Driving a car ...");
  }
}

class Truck {
  drive() {
    console.log("Driving a truck ...");
  }

  loadCargo(amount: number) {
    console.log(`Loading cargo ...${amount}`);
  }
}

type Vehicle = Car | Truck;

function userVehicle(vehicle: Vehicle) {
  vehicle.drive();
  if (vehicle instanceof Truck) {
    vehicle.loadCargo(1000);
  }
}

另外,还可以使用上面的 in"loadCargo" in vehicle

类型转换

使用 as 把一个变量转换成更具体的类型。例如,假设有一个 input 标签:

const userInputElement = document.getElementById(
  "user-input"
) as HTMLInputElement;

userInputElement.value = "Hi, there!";

如果不使用 as 转换成 HTMLInputElement,访问 value 属性时会报错,因为 getElementById 返回的是 HTMLElement 类型,没有 value 属性。

索引属性

假设要对用户信息进行验证,然后给用户一些错误提示。例如我们要验证用户的 emailusername,有可能会出现以下错误:

{
  email: "Not valid email",
  username: "Can't be empty"
}

但是这两个错误不一定都存在,可能只有其中的一个,也可能没有。

对于这种情况,我们想写一个 Interface 来表达这些错误。也许我们可以这样写:

interface ErrorContainer {
  email: string;
  username: string;
}

这样的话,我们声明错误的时候,如果只有一个错误,那么其中一个属性就必须设置为 null,不能省略。如果有更多不同的错误,这种方法就不够灵活。

下面我们来看一下如何用索引属性解决这个问题。

因为 Error 的具体字段暂时不知道,但是我们知道字段类型是 string,字段对应的内容也是 string。所以可以把接口定义为:

interface ErrorContainer {
  [prop: string]: string;
}

[prop: string]: string; 中,第一个 string 代表字段的类型,第二个 string 代表值的类型。

我们的 ErrorContainer 就变得很灵活:

const error1: ErrorContainer = {};

const error2: ErrorContainer = {
  email: "Not valid email"
};

const error3: ErrorContainer = {
  email: "Not valid email",
  username: "Can't be empty"
};

方法重载

假设有以下代码:

type Combinable = string | number;

function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

const combined = add("a", "b");

从上面的代码可以知道,add("a", "b") 的返回值本质上应该是一个 string,但是 TypeScript 只知道它是一个 Combinable 类型。

我们可以通过方法重载,让 add("a", "b") 直接返回个 string 类型:

function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: number, b: string): string;
function add(a: string, b: number): string;
function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

add(a: Combinable, b: Combinable) 上面添加所有可能的参数组合,就可以在调用的时候直接返回对应的返回值。

可选链

假设有以下数据:

const fetchedUserData = {
  id: "1",
  name: "Lebron",
  job: { title: "CEO", description: "My own company" }
};

然后我们打印其中的 title

console.log(fetchedUserData.job.title);

很明显,根据 fetchedUserData,我们是可以成功地打印出 title。但在实际开发中,fetchedUserData 中的部分数据有可能缺失,例如 job 的数据不存在,fetchedUserData 变成了:

const fetchedUserData = {
  id: "1",
  name: "Lebron"
};

这是如果我们再像上面那样去打印 title ,运行的时候就会报错。在 JavaScript 中,我们可以这样去解决问题:

console.log(fetchedUserData.job && fetchedUserData.job.title);

但在 TypeScript 中,我们使用可选链更为简洁:

console.log(fetchedUserData.job?.title);

nullundefined 的处理

在 Javascript 中,我们经常写类似下面的代码:

const input = null;
const storedData = input || "Default";
console.log(storedData);

上面的代码会输出 Default

如果 input 是空字符串,也会输出 Default。但这可能不是我们想要的。我们想在当 inputnull 或者 undefined 时,才默认设置为 Default。在 TypeScript 中,我们可以使用 ?? 来检查 null 或者 undefined

const input = "";
const storedData = input ?? "Default";
console.log(storedData);

?? 只会检查 null 或者 undefined,如果发现是其中一个,才会设置 Default