类型断言
类型断言 可以用来手动指定一个值的类型
语法
值 as 类型
或者
<类型>值
在 tsx 语法中(也就是 React) 必须使用前者,即 值 as 类型
形如
所以建议大家在使用类型断言的时候,统一使用 值 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 才行