一文带你了解TypeScript类型断言与类型保护

た 入场券 2022-12-27 09:06 254阅读 0赞

目录

      • 本文概览:
        1. 类型断言
        1. 双重断言
        1. 类型保护
        • (1)自定义类型保护
        • (2)typeof 类型保护
        • (3)instanceof 类型保护

本文概览:

在这里插入图片描述

1. 类型断言

TypeScrip的类型系统很强大,但是有时它是不如我们更了解一个值的类型。这时,我们更希望TypeScript不要帮我们进行类型检查,而是让我们自己来判断,则就用到了类型断言。

使用类型断言可以手动指定一个值的类型。类型断言像是一种类型转换,它把某个值强行指定为特定类型:

  1. const getLength = target => {
  2. if (target.length) {
  3. return target.length;
  4. } else {
  5. return target.toString().length;
  6. }
  7. };

这个函数接收一个参数,并返回它的长度。这里传入的参数可以是字符串、数组或是数值等类型的值,如果有 length 属性,说明参数是数组或字符串类型,如果是数值类型是没有 length 属性的,所以需要把数值类型转为字符串然后再获取 length 值。现在我们限定传入的值只能是字符串或数值类型的值:

  1. const getLength = (target: string | number): number => {
  2. if (target.length) { // error 类型"string | number"上不存在属性"length"
  3. return target.length; // error 类型"number"上不存在属性"length"
  4. } else {
  5. return target.toString().length;
  6. }
  7. };

当TypeScript不确定一个联合类型的变量到底是哪个类型时,就只能访问此联合类型的所有类型里共有的属性或方法,所以现在加了对参数target和返回值的类型定义之后就会报错。

这时候,我们就用到了断言,将target的类型断言成string类型。它有两种写法,一种是<type>value,一种是value as type

  1. // 这种形式是没有任何问题的写法,建议始终使用这种形式
  2. const getStrLength = (target: string | number): number => {
  3. if ((target as string).length) {
  4. return (target as string).length;
  5. } else {
  6. return target.toString().length;
  7. }
  8. };
  9. // 这种形式在JSX代码中不可以使用,而且也是TSLint不建议的写法
  10. const getStrLength = (target: string | number): number => {
  11. if ((<string>target).length) {
  12. return (<string>target).length;
  13. } else {
  14. return target.toString().length;
  15. }
  16. };

类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。

注意: 类型断言不要滥用,在万不得已的情况下使用要谨慎,因为强制把某类型断言会造成 TypeScript 丧失代码提示的能力。

2. 双重断言

虽然类型断言是有强制性的,但并不是万能的,因为一些情况下也会失效:

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. const person = 'ts' as Person; // Error

这个时候会报错,很显然不能把 string 强制断言为一个接口 Person ,但是并非没有办法,此时可以使用双重断言:

  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. const person = 'ts' as any as Person; // ok

先把类型断言为 any ,再接着断言为想断言的类型就能实现双重断言,当然上面的例子肯定说不通的,双重断言我们也更不建议滥用,但是在一些少见的场景下也有用武之地。

3. 类型保护

类型保护实际上是一种错误提示机制,类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。类型守保护主要思想是尝试检测属性、方法或原型,以确定如何处理值。

先来看一个例子:

  1. const valueList = [123, "abc"];
  2. const getRandomValue = () => {
  3. const number = Math.random() * 10; // 这里取一个[0, 10)范围内的随机值
  4. if (number < 5) {
  5. return valueList[0]; // 如果随机数小于5则返回valueList里的第一个值,也就是123
  6. }else {
  7. return valueList[1]; // 否则返回"abc"
  8. }
  9. };
  10. const item = getRandomValue();
  11. if (item.length) {
  12. // error 类型“number”上不存在属性“length”
  13. console.log(item.length); // error 类型“number”上不存在属性“length”
  14. } else {
  15. console.log(item.toFixed()); // error 类型“string”上不存在属性“toFixed”
  16. }

这个例子中,getRandomValue 函数返回的元素是不固定的,有时返回数值类型,有时返回字符串类型。我们使用这个函数生成一个值 item,然后接下来的逻辑是通过是否有 length 属性来判断是字符串类型,如果没有 length 属性则为数值类型。在 js 中,这段逻辑是没问题的,但是在 TS 中,因为 TS 在编译阶段是无法知道 item 的类型的,所以当在 if 判断逻辑中访问 item 的 length 属性时就会报错,因为如果 item 为 number 类型的话是没有 length 属性的。

这个问题可以通过上面说的类型断言来解决,修改判断逻辑即可:

  1. if ((<string>item).length) {
  2. console.log((<string>item).length);
  3. } else {
  4. console.log((<number>item).toFixed());
  5. }

(1)自定义类型保护

上面的代码不报错是因为通过使用类型断言,告诉 TS 编译器,if 中的 item 是 string 类型,而 else 中的是 number 类型。这样做虽然可以,但是需要在使用 item 的地方都使用类型断言来说明,显然有些繁琐,所以就可以使用类型保护来优化。

可以使用自定义类型保护来解决:

  1. const valueList = [123, "abc"];
  2. const getRandomValue = () => {
  3. const number = Math.random() * 10; // 这里取一个[0, 10)范围内的随机值
  4. if (number < 5) return valueList[0]; // 如果随机数小于5则返回valueList里的第一个值,也就是123
  5. else return valueList[1]; // 否则返回"abc"
  6. };
  7. function isString(value: number | string): value is string {
  8. const number = Math.random() * 10
  9. return number < 5;
  10. }
  11. const item = getRandomValue();
  12. if (isString(item)) {
  13. console.log(item.length); // 此时item是string类型
  14. } else {
  15. console.log(item.toFixed()); // 此时item是number类型
  16. }

首先定义一个函数,函数的参数 value 就是要判断的值,在这个例子中 value 的类型可以为 number 或 string,函数的返回值类型是一个结构为 value is type 的类型谓语,value 的命名无所谓,但是谓语中的 value 名必须和参数名一致。而函数里的逻辑则用来返回一个布尔值,如果返回为 true,则表示传入的值类型为is后面的 type。

使用类型保护后,if 的判断逻辑和代码块都无需再对类型做指定工作,不仅如此,既然 item 是 string 类型,则 else 的逻辑中,item 一定是联合类型两个类型中另外一个,也就是 number 类型。

(2)typeof 类型保护

但是这样定义一个函数来用于判断类型是字符串类型,难免有些复杂,因为在 JavaScript 中,只需要在 if 的判断逻辑地方使用 typeof 关键字即可判断一个值的类型。所以在 TS 中,如果是基本类型,而不是复杂的类型判断,可以直接使用 typeof 来做类型保护:

  1. if (typeof item === "string") {
  2. console.log(item.length);
  3. } else {
  4. console.log(item.toFixed());
  5. }

这样直接写效果和自定义类型保护一样。但是在 TS 中,对 typeof 的处理还有些特殊要求:

  • 只能使用=!两种形式来比较
  • type 只能是numberstringbooleansymbol四种类型,在 TS 中,只会把这四种类型的 typeof 比较识别为类型保护

如果使用typeof {} === ‘object’,那它只是一条普通的 js 语句,不具有类型保护具有的效果:

  1. const valueList = [{ }, () => { }];
  2. const getRandomValue = () => {
  3. const number = Math.random() * 10;
  4. if (number < 5) {
  5. return valueList[0];
  6. } else {
  7. return valueList[1];
  8. }
  9. };
  10. const res = getRandomValue();
  11. if (typeof res === "object") {
  12. console.log(res.toString());
  13. } else {
  14. console.log(ress()); // error 无法调用类型缺少调用签名的表达式。类型“{}”没有兼容的调用签名
  15. }

(3)instanceof 类型保护

instanceof操作符是 JS 中的原生操作符,它用来判断一个实例是不是某个构造函数创建的,或者是不是使用 ES6 语法的某个类创建的。在 TS 中,使用 instanceof 操作符同样会具有类型保护效果,来看例子:

  1. class CreateByClass1 {
  2. public age = 18;
  3. constructor() { }
  4. }
  5. class CreateByClass2 {
  6. public name = "TypeScript";
  7. constructor() { }
  8. }
  9. function getRandomItem() {
  10. return Math.random() < 0.5 ? new CreateByClass1() : new CreateByClass2(); // 如果随机数小于0.5就返回CreateByClass1的实例,否则返回CreateByClass2的实例
  11. }
  12. const item = getRandomItem();
  13. if (item instanceof CreateByClass1) { // 这里判断item是否是CreateByClass1的实例
  14. console.log(item.age);
  15. } else {
  16. console.log(item.name);
  17. }

这个例子中 if 的判断逻辑中使用 instanceof 操作符判断了 item 。如果是 CreateByClass1 创建的,那么它应该有 age 属性,如果不是,那它就有 name 属性。

总结: 通过使用类型保护可以更好地指定某个值的类型,可以把这个指定理解为一种强制转换,这样编译器就能知道这个值是指定的类型,从而符合预期。typeofinstanceof 是JavaScript 中的两个操作符,用来判断某个值的类型和一个值是否是某个构造函数的实例,它们在 TypeScript 中会被当做类型保护。我们也可以自定义类型保护,通过定义一个返回值类型是”参数名 is type”的语句,来指定传入这个类型保护函数的某个参数是什么类型。如果只是简单地要判断某个值是什么类型,使用 typeof 类型保护就可以。

发表评论

表情:
评论列表 (有 0 条评论,254人围观)

还没有评论,来说两句吧...

相关阅读

    相关 typescript类型断言

    作用: 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”,你会比 TypeScript 更了解某个值的详细信息,你清楚的知道一个实体具有比它现有类型更确