Typescript 类型断言 (八)

类型断言

类型断言 可以用来手动指定一个值的类型

语法

值 as 类型

或者

<类型>值

在 tsx 语法中(也就是 React) 必须使用前者,即 值 as 类型

形如的语法在 tsx 中表示的是一个 ReactNode,在 ts 中除了表示类型断言之外,也可以是一个泛型

所以建议大家在使用类型断言的时候,统一使用 值 as 类型 这样的语法

类型断言的用途

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


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.swim 报错的问题了

需要注意的就是,类型断言只能够[欺骗]Typescript 编译器,无法避免运行时候的错误,反而滥用类型断言可导致运行时的错误


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`

上面这个例子在运行的时候就会报错,原因是我们使用类型断言欺骗了编译器,把参数全部变成了 Fish 一种情况,但你要是实际传入 Cat 类型,就会直接报错了

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

当类之间有继承关系的时候,类型断言很常见


class ApiError extends Error {
    code: number = 0;
}
class HttpError extends Error {
    statusCode: number = 200;
}

function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
        return true;
    }
    return false;
}

类型断言的限制

  • 具体来说,若 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
  • 当 Animal 兼容 Cat,他们就可以互相进行类型断言了

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

function testAnimal(animal: Animal) {
    return (animal as Cat);
}
function testCat(cat: Cat) {
    return (cat as Animal);
}

总之,若 A 兼容 B,那么 A 能够被断言为 B,B 也能被断言为 A。

同理,若 B 兼容 A,那么 A 能够被断言为 B,B 也能被断言为 A。

要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可,这也是为了在类型断言时的安全考虑,毕竟毫无根据的断言是非常危险的。

双重断言

  • 双重断言 as any as Foo 来将任何一个类型断言为任何另一个类型呢

interface Cat {
    run(): void;
}
interface Fish {
    swim(): void;
}

function testCat(cat: Cat) {
    return (cat as any as Fish);
}

在上面的例子中,若直接使用 cat as Fish 肯定会报错,因为 Cat 和 Fish 互相都不兼容。

但是若使用双重断言,则可以打破「要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可」的限制,将任何一个类型断言为任何另一个类型。

若你使用了这种双重断言,那么十有八九是非常错误的,它很可能会导致运行时错误。

类型断言 vs 类型转换

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


function toBoolean(something: any): boolean {
    return something as boolean;
}

toBoolean(1);
// 返回值为 1

在上面的例子中,将 something 断言为 boolean 虽然可以通过编译,但是没有什么用,代码在编译后会变成

function toBoolean(something) {
  return something
}

toBoolean(1)
// 返回值为 1

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

若要进行类型转换,需要直接调用类型转换的方法:

function toBoolean(something: any): boolean {
  return Boolean(something)
}

toBoolean(1)
// 返回值为 true

类型断言 vs 类型声明

在这个例子中:


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();

我们使用 as Cat 将 any 类型断言为了 Cat 类型。

但实际上还有其他方式可以解决这个问题


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

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

const tom: Cat = getCacheData('tom');
tom.run();

上面的例子中,我们通过类型声明的方式,将 tom 声明为 Cat,然后再将 any 类型的 getCacheData(‘tom’) 赋值给 Cat 类型的 tom。

这和类型断言是非常相似的,而且产生的结果也几乎是一样的——tom 在接下来的代码中都变成了 Cat 类型。

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


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

const animal: Animal = {
    name: 'tom'
};
let tom = animal as Cat;

在上面的例子中,由于 Animal 兼容 Cat,故可以将 animal 断言为 Cat 赋值给 tom。

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

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

const animal: Animal = {
  name: 'tom',
}
let tom: Cat = animal

// index.ts:12:5 - error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'.

则会报错,不允许将 animal 赋值为 Cat 类型的 tom。

这很容易理解,Animal 可以看作是 Cat 的父类,当然不能将父类的实例赋值给类型为子类的变量。

深入的讲,它们的核心区别就在于:

  • animal 断言为 Cat,只需要满足 Animal 兼容 Cat 或 Cat 兼容 Animal 即可

  • animal 赋值给 tom,需要满足 Cat 兼容 Animal 才行


文章作者: 雾烟云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 雾烟云 !
  目录