Skip to content

TypeScript类型保护机制

Published: at 12:16

什么是类型保护机制?首先来看一段代码还原一下场景。

enum Animal {
  Dog,
  Cat,
}
class Dog {
  dogSay() {
    console.log("汪汪汪");
  }
}
class Cat {
  catSay() {
    console.log("喵喵喵");
  }
}
function animalSay(animal: Animal) {
  let animal = animale === Animal.Dog ? new Dog() : new Cat();
  if (animal.dogSay) {
    animal.dogSay();
  } else {
    animal.catSay();
  }
}

上面代码的意思是声明一个 Dog 类和 Cat 类,然后再声明一个 animalSay 方法并且传入一个 animal 参数,animal 是一个枚举,在函数中判断 animal 是 Dog 还是 Cat 分别来执行类上面的 dogSay 或者 catSay 方法。

聪明的你看到上面代码一定知道在 TypeScript 编译器环境下时报错的。因为编译器并不知道 animal 是 Dog 的实例还是 Cat 的实例,所以在代码中使用类上的方法就会报错。

报错了,很显然就写不了代码了,那么有什么办法可以解决上面出现的问题的?问到这了那必然是有的 😬。

智商 250 以下能想出的办法-类型断言

类型断言是一种比较笨的办法,因为极大的增加了我们的代码量的同时类型断言也并不那么安全。

来改造一下 animalSay 函数。

function animalSay(animal: Animal) {
  let animal = animal === Animal.Dog ? new Dog() : new Cat();
  if ((animal as Dog).dogSay) {
    (animal as Dog).dogSay();
  } else {
    (animal as Cat).catSay();
  }
}

这样代码就不报错了,代码写错来上面提的问题也就显现出来。下面来看一种比较好的方法。

智商 250 以上能想出的办法-类型保护机制

什么是类型保护机制呢?TypeScript 能够在特定的区块中保证变量是属于某种特定的类型的,可以在此区块中放心的引用此类型的属性或者调用此类型的方法。

类型保护机制一共有四种方法:

step1: instanceof 运算符

我们都知道 instanceof 是判断一个对象在另一个对象的原型链上,使用这个特定也可以用来做类型保护。修改 animalSay 函数如下:

function animalSay(animal: Animal) {
  let animal = animal === Animal.Dog ? new Dog() : new Cat();
  if (animal instanceof Dog) {
    animal.dogSay();
  } else {
    animal.catSay();
  }
}

step2:in 运算符

in 运算符的作用是判断一个属性是否存在于一个对象身上。使用 in 运算符做类型保护的话需要在 Dog 类和 Cat 类型增加一个 check 属性用来做 in 运算符的判断。修改代码如下:

class Dog {
  dogSay() {
    console.log("汪汪汪");
  }
  isDog: true;
}
class Cat {
  catSay() {
    console.log("喵喵喵");
  }
  isCat: true;
}
function animalSay(animal: Animal) {
  let animal = animal === Animal.Dog ? new Dog() : new Cat();
  if ("isDog" in animal) {
    animal.dogSay();
  } else {
    animal.catSay();
  }
}

step3:typeof 运算符

typeof 最基本的作用是监测一个值的类型是什么(虽然监测的也不准)。 typeof 运算符并不能用于上面例子,因为都是 Object 类型,它只能作用于它能监测出来的类型上。 例如:

function log(value: string | number) {
  if (typeof value === "number") {
    console.log(value.toFixed(2));
  } else {
    console.log(value.length);
  }
}

此时上面的代码就不会报错,通过 typeof 运算符当监测出 value 是 number 类型的时候使用 number 类型上面的方法,否则就是 string 类型可以使用 string 类型上的属性或方法。

step4:借用类型保护函数

首先我们要声明一个类型保护函数,这个类型保护函数的返回值是由类型谓词决定的一个 boolean 值。

function isDog(animal: Dog | Cat): animal is Dog {
  return (animal as Dog).dogSay !== undefined;
}

上面类型保护判断用于判断一个 animal 参数是否是 Dog 类的实例,通过判断 animal 上 dogSay 方法是否存在来判断是否是一个 Dog 类的实例,注意返回值类型必须是一个类型谓词is修饰的,不然会不起作用。修改下 animalSay 函数如下:

function animalSay(animal: Animal) {
  let animal = animal === Animal.Dog ? new Dog() : new Cat();
  if (isDog(animal)) {
    animal.dogSay();
  } else {
    animal.catSay();
  }
}

总结

很显然使用类型保护机制比类型断言好多了,而且还增加了类型的安全性。通过上面四种类型保护机制的分析基本上涵盖了所有的场景,可以放心大胆的在项目中使用。