【TypeScript专题】之类型断言

朴灿烈づ我的快乐病毒、 2022-09-04 00:55 522阅读 0赞

【TypeScript 专题】之类型断言

断言是编程术语,表示为一些布尔表达。—— 百度百科

系列文章,收藏不走丢哦

类型断言(Type Assertion)可以用来手动指定一个值的类型。就是告诉编译器, 你不要帮我们检查了, 相信我,它就是这个类型。

在这里插入图片描述

一、Ts 中的断言

断言就好比解释型强制类型转换,直接告诉你这个变量是什么样子的!

  1. as 类型;

先来看看例子:

  1. interface fruits {
  2. name: string;
  3. getColor(): void;
  4. }
  5. interface person {
  6. name: string;
  7. getAge(): void;
  8. }
  9. type fruitsOrPerson = fruits | person;
  10. function getName(intance: fruitsOrPerson) {
  11. // 使用联合类型共有的属性,是可以的
  12. return intance.name;
  13. }

而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,就像这样:

  1. interface fruits {
  2. name: string;
  3. getColor(): void;
  4. }
  5. interface person {
  6. name: string;
  7. getAge(): void;
  8. }
  9. type fruitsOrPerson = fruits | person;
  10. function getColor(intance: fruitsOrPerson) {
  11. return intance.getColor();
  12. }
  13. // 类型“fruitsOrPerson”上不存在属性“getColor”。
  14. // 类型“person”上不存在属性“getColor”

上面的例子中,执行intance.getColor 的时候会报错。这是因为在它类型不确定时,我们使用了非共有属性或方法。

此时可以使用类型断言,将实例断言成fruits

  1. interface fruits {
  2. name: string;
  3. getColor(): void;
  4. }
  5. interface person {
  6. name: string;
  7. getAge(): void;
  8. }
  9. type fruitsOrPerson = fruits | person;
  10. function getColor(intance: fruitsOrPerson) {
  11. return (intance as fruits).getColor(); // (method) fruits.getColor(): void
  12. }

需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,不合理的使用会导致不给掌控的错误,例如:

  1. interface fruits {
  2. name: string;
  3. getColor(): void;
  4. }
  5. interface person {
  6. name: string;
  7. getAge(): void;
  8. }
  9. type fruitsOrPerson = fruits | person;
  10. // 通过
  11. function getColor(intance: fruitsOrPerson) {
  12. return (intance as fruits).getColor();
  13. }
  14. // 执行错误 intance.getColor is not a function
  15. getColor({
  16. name: "余光",
  17. getAge: () => {
  18. return 100;
  19. },
  20. });

上面的例子编译时不会报错,但在运行时会报错:

使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性(就像上面的例子),以减少不必要的运行时错误。

在这里插入图片描述

二、将一个父类断言为更加具体的子类

之间有继承关系时,类型断言也是很常见的:

  1. class ApiError extends Error {
  2. code: number = 0;
  3. }
  4. class HttpError extends Error {
  5. statusCode: number = 200;
  6. }
  7. function isApiError(error: Error) {
  8. if (typeof (error as ApiError).code === "number") {
  9. return true;
  10. }
  11. return false;
  12. }

上面的例子中,我们声明了函数isApiError,它用来判断传入的参数是不是ApiError类型,为了实现这样一个函数,为了保证提前使用不确定的code属性,需要使用类型断言手动指定类型以跳过类型检查。

读到这里大家是否意识到类型断言被创造出来的目的是什么?

三、将任何一个类型断言为any

理想情况下,TypeScript的类型系统运转良好,每个值的类型都具体而精确,报错 === 代码不够严谨!但下面的代码也会报错怎么办呢?

  1. const val: number = 1;
  2. val.length = 1; // 类型“number”上不存在属性“length”

上面的例子中,数字类型的变量val上是没有length属性的,Ts会帮我们提前发现问题,并暴露出来。

但有的时候我们非常确定这段代码不会出错!!,比如下面这个例子:

  1. window.foo = 1; // 添加一个属性,并进行赋值

在Ts中这段代码会报错,因为window上没有foo属性,为了应对这种情况,我们希望可以访问任何属性:

此时我们可以使用as any临时将window断言为any类型:

  1. (window as any).foo = 1; //

在 any 类型的变量上,访问任何属性都是允许的。

Ts将类型断言的松紧尺度交给我们自己,是通篇as any还是合理的使用它,是我们需要平衡的。

在这里插入图片描述

四、类型断言与其他类型限制的区别

类型断言实际上就是在检测阶段帮我们消除错误的一种手段,有些“暴力”但关键时刻很有用,那么它和其他类型声明的具体区别在哪里呢?

4.1 类型断言 vs 类型转换

类型断言不是类型转换,它不会真的影响到变量的类型。

类型断言只会影响TypeScript编译时的类型,类型断言语句在编译结果中会被删除:

  1. function toBoolean(something: any): boolean {
  2. return something as boolean;
  3. }
  4. toBoolean(1); // 1

在这里插入图片描述

在上面的例子中,将something断言为boolean虽然可以通过编译,但看上图,实际编译过后类型并没有被转换。

4.2 类型断言 vs 类型声明

类型声明是比类型断言更加严格的

先来看下面的代码

  1. function getCacheData(key: string): any {
  2. // 1. window断言为any才能调用cache方法
  3. return (window as any).cache[key];
  4. }
  5. interface Cat {
  6. name: string;
  7. run(): void;
  8. }
  9. // 2. getCacheData的返回值 as Cat才能调用Cat的方法
  10. const tom = getCacheData("tom") as Cat;
  11. tom.run();

在1、2注释中我们解释了为什么要使用断言,再来看看同样的情况(不使用断言)要如果做:

  1. function getCacheData(key: string): any {
  2. return (window as any).cache[key];
  3. }
  4. // 定义Cat形状的接口
  5. interface Cat {
  6. name: string;
  7. run(): void;
  8. }
  9. // 为tom指定类型
  10. const tom: Cat = getCacheData("tom");
  11. tom.run();

看上去直观了一点,当然区别不仅仅是这样的

它们的区别,可以通过这个例子来理解:

  1. interface Animal {
  2. name: string;
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. // animal 符合 Animal 的形状
  9. const animal: Animal = {
  10. name: "tom",
  11. };
  12. // animal 是Cat类型的
  13. let tom = animal as Cat;

但是若直接声明 tom 为 Cat 类型:

  1. interface Animal {
  2. name: string;
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. // animal 符合 Animal 的形状
  9. const animal: Animal = {
  10. name: "tom",
  11. };
  12. // tom是Cat,并初始化为animal
  13. let tom: Cat = animal; // Error: 类型 "Animal" 中缺少属性 "run",但类型 "Cat" 中需要该属性

它们的核心区别就在于:

  • animal 断言为 Cat,只需要满足Animal兼容CatCat兼容Animal即可(疑问的小伙伴可以将“兼容”理解成“属性或方法的重叠”)
  • animal 赋值给 tom,需要满足Cat兼容Animal才行

所以为了增加代码的质量,我们最好优先使用类型声明,这也比类型断言的 as 语法更加优雅。

写在最后

本篇文章是《Typescript 专题》第二篇,本篇文章主要聊一聊类型断言是什么,一句话概括就是——类型断言的目的就是让编译通过,是不是很刺激?欢迎小伙伴们积极踊跃的互动,我会及时回复的哦,下一篇我们聊一聊大家最感兴趣的吧~

系列文章传送门:

  1. 【Typescript入门手册】之基本类型在 TypeScript 中的应用
  2. 【Typescript入门手册】之引用类型在 TypeScript 中的应用
  3. 【Typescript入门手册】之函数类型在 TypeScript 中的应用
  4. 【Typescript专题】之类型进阶
  5. 本文

关于我

其他沉淀

  • Js版LeetCode题解
  • 前端进阶笔记
  • CSDN 博客汇总

blog-bottom.gif

发表评论

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

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

相关阅读

    相关 typescript类型断言

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