ts中的类型断言

143 阅读4分钟

定义(作用):

顾名思义,对某个值的类型进行推断,类型断言(Type Assertion)可以用来手动指定一个值的类型。

类型断言的两种书写方式:

(1)使用as (2)使用<>

为了更好的兼容,建议大家在使用类型断言时,统一使用 值 as 类型 这样的语法。

类型断言的用途

  • 将一个联合类型断言为其中一个类型。

    我们觉得自己比编译器更清除某个值的类型的时候可以使用类型断言

    当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法,而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法.我们采用断言将其指定为一个类型。(这么做只是“欺骗了”ts,让其信任我们所指定的类型,不这样做可能会报错)

案例一:
类型断言书写方式一(使用as):

联合类型没有使用类型断言时候:

image.png

使用类型断言以后(相当于告诉编译器input就是string类型)

image.png

完整代码:

image.png

类型断言书写方式二(使用<>):

image.png

断言就是告诉浏览器,input类型就是string

案例二:
```
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === 'function') {
        return true;
    }
    return false;
}
//(animal as Fish)要用括号,断言animal为Fish以后,就可以直接使用Fish中的方法,   不会报错,并且使用前编译器会有提示

// 乱使用类型断言虽然在编译期间无问题,运行时依然报错
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function swim(animal: Cat | Fish) {
    (animal as Fish).swim();
}

const tom: Cat = {
    name: 'Tom',
    run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
```

应用场景:

应用场景 1: 获取 DOM 元素的时候指定具体元素
const box = document.getElementById('img')
console.log(box.src) // ts报错

如下 :

image.png

解析 :

上述语法在 js 中可以直接使用, 但是 TS 中就不行, 由于类型不明确会报错

返回值的类型是 HTMLElement,只有标签公共的属性,不包含 img 的 src 属性

解决方法 :

使用类型断言明确具体类型

语法 :  let 变量 = 值 as 类型

  • as 关键字实现类型断言
  • 关键字 as 后面的类型是一个具体的类型

实例 :

  • const box = document.getElementById('img') as HTMLImageElement
  • 指定了具体类型后 , 就可以使用 box.src

技巧: 获取 DOM 元素类型的方法

  • 在浏览器控制台,通过 __proto__ 获取 DOM 元素的类型;
  • document.createElement('a'),然后看代码提示

image.png

应用场景 2:知道获取数据的类型, 但是不知道具体的数据时 , 使用类型断言后就会有提示
type User = {
  name: string,
  age: number
}

const u1 = {} as User

console.log(u1.name) // 这里就会有提示
  • 将一个父类断言为更加具体的子类

class ApiError extends Error {
        code: number = 0;
    }
    class HttpError extends Error {
        statusCode: number = 200;
    }
    // 使用该函数判断错误类型是何
    // 它的参数的类型是比较抽象的父类 Error,这样的话这个函数就能接受 Error 或它的子类作为参数了
    //但是由于父类 Error 中没有 code 属性,会报错,需要使用类型断言获取 (error as ApiError).code
    function isApiError(error: Error) {
          if (typeof (error as ApiError).code === 'number') {
            return true;
        }
        return false;
    }
    复制代码

实话说肯定是instanceof更为合适去判断子类父类的。但是有时候 ApiError 和 HttpError 不是一个真正的类,而只是一个 接口(interface),不是一个真正的值,它在编译成js后会被删除,无法使用 instanceof 来做判断了:

  interface ApiError extends Error {
      code: number;
  }
  interface HttpError extends Error {
      statusCode: number;
  }
  
  function isApiError(error: Error) {
      if (error instanceof ApiError) {
          return true;
      }
      return false;
  }
  • 将任何一个类型断言为 any

window.foo = 1; // 我们非常确定这段代码不会出错,但ts编译时还是会报错
// 改写
(window as any).foo = 1;
  • 将 any 断言为一个具体的类型

可能是历史的原因,也可能是以前那位编写库的程序员水平不足,使得库内的一些函数返回值为any。如果不管理他,类型推断会使得产生更多的any,我们适当的在使用的时候令其断言为具体的类型而不是任由其发展。 在我们使用的时候将其断言为一个具体的类型

function getCacheData(key: string): any {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

const tom = getCacheData('tom') as Cat;
tom.run()
  • 类型断言的限制

并不是任何一个类型都可以被断言为任何另一个类型。

具体来说,若 A 兼容 B,那么 A 能够被断言为 B,B 也能被断言为 A。

下面我们通过一个简化的例子,来理解类型断言的限制:

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

let tom: Cat = {
    name: 'Tom',
    run: () => { console.log('run') }
};
let animal: Animal = tom;

在上面的例子中,Cat 包含了 Animal 中的所有属性,除此之外,它还有一个额外的方法 run。

TypeScript 只会看它们最终的结构有什么关系——所以它与 Cat extends Animal 是等价的:

interface Animal {
    name: string;
}
interface Cat extends Animal {
    run(): void;
}

那么也不难理解为什么 Cat 类型的 tom 可以赋值给 Animal 类型的 animal 了——就像面向对象编程中我们可以将子类的实例赋值给类型为父类的变量。

我们把它换成 TypeScript 中更专业的说法,即:Animal 兼容 Cat。

当 Animal 兼容 Cat 时,它们就可以互相进行类型断言了

  • 双重断言

    • 任何类型都可以被断言为 any
    • any 可以被断言为任何类型

    那么我们是不是可以使用双重断言 as any as Foo 来将任何一个类型断言为任何另一个类型呢? 可以,但永远不要这么使用。

  • 类型断言 vs 类型声明

function getCacheData(key: string): any {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}
// 第一种
const tom = getCacheData('tom') as Cat;
// 第二种,更为严格,适用
const tom: Cat = getCacheData('tom');

tom.run();
  • 类型断言 vs 泛型

// 使用泛型是解决上面那个例子的最好方案

function getCacheData<T>(key: string): T {
    return (window as any).cache[key];
}

interface Cat {
    name: string;
    run(): void;
}

const tom = getCacheData<Cat>('tom');
tom.run();