类型断言(Type Assertion)可以用来手动指定一个值的类型。
语法
用途
联合类型断言为其中一个类型
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型中共有的属性或方法,但是可以使用类型断言,访问其中一个类型特有的属性或方法:
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;}
注意,类型断言只能够「欺骗」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('没有swim方法') }};swim(tom);// 编译时不会报错,但在运行时会报错:Uncaught TypeError: animal.swim is not a function
父类断言为某个具体的子类
当类之间有继承关系时,类型断言也是很常见的:
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;}
不过对类来说,更适合的判断方式是使用 **instanceof**!
//接口是一个类型,不是一个真正的值,在编译后会被删除,就无法使用 instanceof 来做运行时判断function isApiError(error: Error) {if (error instanceof ApiError) {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;}
const断言
非空断言!
最常见的非空断言,在表达式后面加个操作符操作符 ! ,可以用于断言操作对象是非 null 和非 undefined 类型。
在生成的 ES5 代码中,! 非空断言操作符会被移。如果没有十足的把握,尽量不要在代码中使用非空断言。
确定赋值断言:
允许在变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性一定会被赋值
类型断言 vs 类型声明
用类型声明可以解决或替换用类型断言的场景
interface Animal {name: string;}interface Cat {name: string;run(): void;}const animal: Animal = {name: 'tom'};let tom = animal as Cat;
类型声明是比类型断言更加严格,为了增加代码的质量,最好优先使用类型声明,这也比类型断言的 as 语法更加优雅。
interface Animal {name: string;}interface Cat {name: string;run(): void;}const animal: Animal = {name: 'tom'};let tom: Cat = animal;// 报错:类型 "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();
可以更加规范的实现对 getCacheData 返回值的约束,这也同时去除掉了代码中的 any,是最优的一个解决方案。```typescriptfunction getCacheData<T>(key: string): T {return (window as any).cache[key];}interface Cat {name: string;run(): void;}const tom = getCacheData<Cat>('tom');tom.run();
类型断言 vs in
in 操作符可以安全的检查一个对象上是否存在一个属性,它通常也被作为类型保护使用:
interface A {x: number;}interface B {y: string;}function doStuff(q: A | B) {if ('x' in q) {// q: A} else {// q: B}}
类型断言 vs 字面量类型
字符串字面量也作为一个类型,在联合类型里使用字面量类型时,可以检查它们是否有区别:
type Foo = {kind: 'foo'; // 字面量类型foo: number;};type Bar = {kind: 'bar'; // 字面量类型bar: number;};function doStuff(arg: Foo | Bar) {if (arg.kind === 'foo') {console.log(arg.foo); // okconsole.log(arg.bar); // Error} else {// 一定是 Barconsole.log(arg.foo); // Errorconsole.log(arg.bar); // ok}}
可以理解为字面量类型就是另起了一个别名用于区分使用(如import a as b from 'c'),最好别用来作类型注解,不然还得重复写一遍字面量类型。
类型断言 vs 自定义类型保护
JavaScript 并没有内置非常丰富的、运行时的自我检查机制。当使用普通的 JavaScript 对象时(使用结构类型,更有益处),甚至无法访问 instanceof 和 typeof。在这种情景下,可以创建用户自定义的类型保护函数,这仅仅是一个返回值为类似于someArgumentName is SomeType 的函数,如下:
// 仅仅是一个 interfaceinterface Foo {foo: number;common: string;}interface Bar {bar: number;common: string;}// 用户自己定义的类型保护!function isFoo(arg: Foo | Bar): arg is Foo {return (arg as Foo).foo !== undefined;}// 用户自己定义的类型保护使用用例:function doStuff(arg: Foo | Bar) {if (isFoo(arg)) {console.log(arg.foo); // okconsole.log(arg.bar); // Error} else {console.log(arg.foo); // Errorconsole.log(arg.bar); // ok}}doStuff({ foo: 123, common: '123' });doStuff({ bar: 123, common: '123' });
双重断言
前置:
- 任何类型都可以被断言为 any
- any 可以被断言为任何类型
应用:使用双重断言,则可以打破「要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 」的限制,将任何一个类型断言为任何另一个类型。
除非迫不得已,千万别用双重断言。
interface Cat {run(): void;}interface Fish {swim(): void;}function testCat(cat: Cat) {// 直接使用 cat as Fish 肯定会报错,因为 Cat 和 Fish 互不兼容。return cat as any as Fish;}
