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

语法

  1. as 类型

  1. <类型>值

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

类型断言的用途

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

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法
function reverse(x:number): number;
function reverse(x:string): string;
function reverse(x:number | string): number | string {
if (typeof x === ‘number’) {
return Number(x.toString().split(‘’).reverse().join(‘’));
} else if (typeof x === ‘string’) {
return x.split(‘’).reverse().join(‘’);
};
};

console.log(reverse(12345));

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

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

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

将任何一个类型断言为 any

理想情况下,TypeScript 的类型系统运转良好,每个值的类型都具体而精确。
当我们引用一个在此类型上不存在的属性或方法时,就会报错:

  1. const foo: number = 1;
  2. foo.length = 1;
  3. // index.ts:2:5 - error TS2339: Property 'length' does not exist on type 'number'.

上面的例子中,数字类型的变量 foo 上是没有 length 属性的,故 TypeScript 给出了相应的错误提示。

一方面不能滥用 as any,另一方面也不要完全否定它的作用,我们需要在类型的严格性和开发的便利性之间掌握平衡(这也是 TypeScript 的设计理念之一),才能发挥出 TypeScript 最大的价值。

any 断言为一个具体的类型

在日常的开发中,我们不可避免的需要处理 any 类型的变量,它们可能是由于第三方库未能定义好自己的类型,也有可能是历史遗留的或其他人编写的烂代码,还可能是受到 TypeScript 类型系统的限制而无法精确定义类型的场景。

遇到 any 类型的变量时,我们可以选择无视它,任由它滋生更多的 any

我们也可以选择改进它,通过类型断言及时的把 any 断言为精确的类型,亡羊补牢,使我们的代码向着高可维护性的目标发展。
举例来说,历史遗留的代码中有个 getCacheData,它的返回值是 any

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

那么我们在使用它时,最好能够将调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作:

  1. function getCacheData(key: string): any {
  2. return (window as any).cache[key];
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const tom = getCacheData('tom') as Cat;
  9. tom.run();

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

类型断言的限制

从上面的例子中,我们可以总结出:

  • 联合类型可以被断言为其中一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为 any
  • any 可以被断言为任何类型

类型断言 vs 类型转换

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

  1. function toBoolean(something: any): boolean {
  2. return something as boolean;
  3. }
  4. toBoolean(1);
  5. // 返回值为 1

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

  1. function toBoolean(something) {
  2. return something;
  3. }
  4. toBoolean(1);
  5. // 返回值为 1

所以类型断言不是类型转换,它不会真的影响到变量的类型。
若要进行类型转换,需要直接调用类型转换的方法:

  1. function toBoolean(something: any): boolean {
  2. return Boolean(something);
  3. }
  4. toBoolean(1);
  5. // 返回值为 true

类型断言 vs 类型声明

在这个例子中:

  1. function getCacheData(key: string): any {
  2. return (window as any).cache[key];
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const tom = getCacheData('tom') as Cat;
  9. tom.run();

我们使用 as Catany 类型断言为了 Cat 类型。
但实际上还有其他方式可以解决这个问题:

  1. function getCacheData(key: string): any {
  2. return (window as any).cache[key];
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const tom: Cat = getCacheData('tom');
  9. tom.run();

上面的例子中,我们通过类型声明的方式,将 tom 声明为 Cat,然后再将 any 类型的 getCacheData('tom') 赋值给 Cat 类型的 tom
这和类型断言是非常相似的,而且产生的结果也几乎是一样的——tom 在接下来的代码中都变成了 Cat 类型。

由于 getCacheData('tom')any 类型,any 兼容 CatCat 也兼容 any,故

  1. const tom = getCacheData('tom') as Cat;

等价于

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

知道了它们的核心区别,就知道了类型声明是比类型断言更加严格的。
所以为了增加代码的质量,我们最好优先使用类型声明,这也比类型断言的 as 语法更加优雅。

类型断言 vs 泛型

  1. function getCacheData(key: string): any {
  2. return (window as any).cache[key];
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const tom = getCacheData('tom') as Cat;
  9. tom.run();

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

  1. function getCacheData<T>(key: string): T {
  2. return (window as any).cache[key];
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const tom = getCacheData<Cat>('tom');
  9. tom.run();

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