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

本文概览:

在这里插入图片描述

1. 类型断言

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

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

const getLength = target => {
  if (target.length) {
    return target.length;
  } else {
    return target.toString().length;
  }
};

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

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

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

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

// 这种形式是没有任何问题的写法,建议始终使用这种形式
const getStrLength = (target: string | number): number => {
  if ((target as string).length) {      
    return (target as string).length; 
  } else {
    return target.toString().length;
  }
};

// 这种形式在JSX代码中不可以使用,而且也是TSLint不建议的写法
const getStrLength = (target: string | number): number => {
  if ((<string>target).length) {      
    return (<string>target).length; 
  } else {
    return target.toString().length;
  }
};

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

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

2. 双重断言

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

interface Person {
	name: string;
	age: number;
}
const person = 'ts' as Person; // Error

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

interface Person {
	name: string;
	age: number;
}
const person = 'ts' as any as Person; // ok

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

3. 类型保护

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

先来看一个例子:

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

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

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

if ((<string>item).length) {
  console.log((<string>item).length);
} else {
  console.log((<number>item).toFixed());
}
(1)自定义类型保护

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

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

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

首先定义一个函数,函数的参数 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 来做类型保护:

if (typeof item === "string") {
  console.log(item.length);
} else {
  console.log(item.toFixed());
}

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

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

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

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

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

class CreateByClass1 {
  public age = 18;
  constructor() {}
}
class CreateByClass2 {
  public name = "TypeScript";
  constructor() {}
}
function getRandomItem() {
  return Math.random() < 0.5 ? new CreateByClass1() : new CreateByClass2(); // 如果随机数小于0.5就返回CreateByClass1的实例,否则返回CreateByClass2的实例
}
const item = getRandomItem();
if (item instanceof CreateByClass1) { // 这里判断item是否是CreateByClass1的实例
  console.log(item.age);
} else {
  console.log(item.name);
}

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CUG-GZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值