feng xiaohan

类型断言(*)

类型断言(Type Assertion)可以用来手动指定一个值的类型。

值 as 类型;

<类型>值;
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
let strLength: number = (someValue as string).length;

用途

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

    此时可以使用类型断言,将 animal 断言成 Fish,以此欺骗编译器,避免访问animal.swim 时报错的问题

    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;
    }
    

    但在运行时可能会报错。总之,使用类型断言时一定要格外小心,尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误。

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

    interface ApiError extends Error {
      code: number;
    }
    interface HttpError extends Error {
      statusCode: number;
    }
    
    function isApiError(error: Error) {
      if (typeof (error as ApiError).code === "number") {
        return true;
      }
      return false;
    }
    

    上面的例子中,声明了函数 isApiError,它用来判断传入的参数是不是 ApiError 类型,为了实现这样一个函数,它的参数的类型肯定得是比较抽象的父类 Error,这样的话这个函数就能接受 Error 或它的子类作为参数了。

    但是由于父类 Error 中没有 code 属性,故直接获取 error.code 会报错,需要使用类型断言获取 (error as ApiError).code

  • 将任何一个类型断言为 any

    一般用于使用 any 进行临时断言。

    (window as any).foo = 1;
    

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

    需要注意的是,将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后一个手段。

    它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as 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();
    

    上面的例子中,我们调用完 getCacheData 之后,立即将它断言为 Cat 类型。这样的话明确了 tom 的类型,后续对 tom 的访问时就有了代码补全,提高了代码的可维护性。

限制

  • 联合类型可以被断言为其中一个类型

  • 父类可以被断言为子类

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

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

  • 要使得 A 能够被断言为 B,只需要 A 兼容 BB 兼容 A 即可

  • 类型断言并不是任何时候都可以成功,不可滥用:

    const fn = (type: any): boolean => {
      return type as boolean;
    };
    
    let cb = fn(1);
    console.log(cb); // 1
    

双重断言

除非迫不得已,千万别用双重断言。

类型断言 vs 类型转换

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

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

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

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

类型断言 vs 类型声明

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

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

类型断言 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();

我们还有第三种方式可以解决这个问题,那就是泛型:

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

通过给 getCacheData 函数添加了一个泛型 <T>,我们可以更加规范的实现对 getCacheData 返回值的约束,这也同时去除掉了代码中的 any,是最优的一个解决方案。