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

语法

值 as 类型

用途

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

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型中共有的属性或方法,但是可以使用类型断言,访问其中一个类型特有的属性或方法:

  1. interface Cat {
  2. name: string;
  3. run(): void;
  4. }
  5. interface Fish {
  6. name: string;
  7. swim(): void;
  8. }
  9. function isFish(animal: Cat | Fish) {
  10. if (typeof (animal as Fish).swim === 'function') {
  11. return true;
  12. }
  13. return false;
  14. }

注意,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,滥用类型断言可能会导致运行时错误:

  1. interface Cat {
  2. name: string;
  3. run(): void;
  4. }
  5. interface Fish {
  6. name: string;
  7. swim(): void;
  8. }
  9. function swim(animal: Cat | Fish) {
  10. (animal as Fish).swim();
  11. }
  12. const tom: Cat = {
  13. name: 'Tom',
  14. run() { console.log('没有swim方法') }
  15. };
  16. swim(tom);
  17. // 编译时不会报错,但在运行时会报错:Uncaught TypeError: animal.swim is not a function

父类断言为某个具体的子类

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

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

不过对类来说,更适合的判断方式是使用 **instanceof**

  1. //接口是一个类型,不是一个真正的值,在编译后会被删除,就无法使用 instanceof 来做运行时判断
  2. function isApiError(error: Error) {
  3. if (error instanceof ApiError) {
  4. return true;
  5. }
  6. return false;
  7. }

不过前提一定要是类,不能是接口!

  1. // 接口必须要使用类型断言
  2. interface ApiError extends Error {
  3. code: number;
  4. }
  5. interface HttpError extends Error {
  6. statusCode: number;
  7. }
  8. function isApiError(error: Error) {
  9. if (typeof (error as ApiError).code === 'number') {
  10. return true;
  11. }
  12. return false;
  13. }

const断言

实现的功能类似于常量枚举
image.png

非空断言!

最常见的非空断言,在表达式后面加个操作符操作符 ! ,可以用于断言操作对象是非 null 和非 undefined 类型。
image.png
在生成的 ES5 代码中,! 非空断言操作符会被移。如果没有十足的把握,尽量不要在代码中使用非空断言。
确定赋值断言:
允许在变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性一定会被赋值
image.png
image.png

类型断言 vs 类型声明

用类型声明可以解决或替换用类型断言的场景

  1. interface Animal {
  2. name: string;
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const animal: Animal = {
  9. name: 'tom'
  10. };
  11. let tom = animal as Cat;

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

  1. interface Animal {
  2. name: string;
  3. }
  4. interface Cat {
  5. name: string;
  6. run(): void;
  7. }
  8. const animal: Animal = {
  9. name: 'tom'
  10. };
  11. let tom: Cat = animal;
  12. // 报错:类型 "Animal" 中缺少属性 "run",但类型 "Cat" 中需要该属性

核心区别在于:

  • animal 断言为 Cat,只需要满足 Animal 兼容 Cat 或 Cat 兼容 Animal 即可
  • animal 赋值给 tom,需要满足 Cat 兼容 Animal 才行

    类型断言 vs 泛型

    用泛型也可以解决或替换用类型断言的场景 ```typescript 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();

  1. 可以更加规范的实现对 getCacheData 返回值的约束,这也同时去除掉了代码中的 any,是最优的一个解决方案。
  2. ```typescript
  3. function getCacheData<T>(key: string): T {
  4. return (window as any).cache[key];
  5. }
  6. interface Cat {
  7. name: string;
  8. run(): void;
  9. }
  10. const tom = getCacheData<Cat>('tom');
  11. tom.run();

类型断言 vs in

in 操作符可以安全的检查一个对象上是否存在一个属性,它通常也被作为类型保护使用:

  1. interface A {
  2. x: number;
  3. }
  4. interface B {
  5. y: string;
  6. }
  7. function doStuff(q: A | B) {
  8. if ('x' in q) {
  9. // q: A
  10. } else {
  11. // q: B
  12. }
  13. }

类型断言 vs 字面量类型

字符串字面量也作为一个类型,在联合类型里使用字面量类型时,可以检查它们是否有区别:

  1. type Foo = {
  2. kind: 'foo'; // 字面量类型
  3. foo: number;
  4. };
  5. type Bar = {
  6. kind: 'bar'; // 字面量类型
  7. bar: number;
  8. };
  9. function doStuff(arg: Foo | Bar) {
  10. if (arg.kind === 'foo') {
  11. console.log(arg.foo); // ok
  12. console.log(arg.bar); // Error
  13. } else {
  14. // 一定是 Bar
  15. console.log(arg.foo); // Error
  16. console.log(arg.bar); // ok
  17. }
  18. }

可以理解为字面量类型就是另起了一个别名用于区分使用(如import a as b from 'c'),最好别用来作类型注解,不然还得重复写一遍字面量类型。

类型断言 vs 自定义类型保护

JavaScript 并没有内置非常丰富的、运行时的自我检查机制。当使用普通的 JavaScript 对象时(使用结构类型,更有益处),甚至无法访问 instanceof 和 typeof。在这种情景下,可以创建用户自定义的类型保护函数,这仅仅是一个返回值为类似于someArgumentName is SomeType 的函数,如下:

  1. // 仅仅是一个 interface
  2. interface Foo {
  3. foo: number;
  4. common: string;
  5. }
  6. interface Bar {
  7. bar: number;
  8. common: string;
  9. }
  10. // 用户自己定义的类型保护!
  11. function isFoo(arg: Foo | Bar): arg is Foo {
  12. return (arg as Foo).foo !== undefined;
  13. }
  14. // 用户自己定义的类型保护使用用例:
  15. function doStuff(arg: Foo | Bar) {
  16. if (isFoo(arg)) {
  17. console.log(arg.foo); // ok
  18. console.log(arg.bar); // Error
  19. } else {
  20. console.log(arg.foo); // Error
  21. console.log(arg.bar); // ok
  22. }
  23. }
  24. doStuff({ foo: 123, common: '123' });
  25. doStuff({ bar: 123, common: '123' });

双重断言

前置:

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

应用:使用双重断言,则可以打破「要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 」的限制,将任何一个类型断言为任何另一个类型。
除非迫不得已,千万别用双重断言。

  1. interface Cat {
  2. run(): void;
  3. }
  4. interface Fish {
  5. swim(): void;
  6. }
  7. function testCat(cat: Cat) {
  8. // 直接使用 cat as Fish 肯定会报错,因为 Cat 和 Fish 互不兼容。
  9. return cat as any as Fish;
  10. }