Skip to content

Latest commit

 

History

History
332 lines (248 loc) · 6.7 KB

7.类型断言.md

File metadata and controls

332 lines (248 loc) · 6.7 KB

类型断言

可以用来手动指定一个值的类型

类型断言的用途

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

当typescript不确定一个联合类型的变量到底是什么类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法

interface Cat {
    name:string;
    run():void;
}
interface Fish {
    name:string;
    swim():void;
}

function getName (animal: Cat | Fish) {
    return animal.name;
}

有时候我们确实还不确定类型的时候就访问其中一个类型特有的属性或方法

interface Cat {
    name:string;
    run():void;
}
interface Fish {
    name:string;
    swim():void;
}

function isFish(animal: Cat | Fish) {
    if(typeof animal.swim === 'function' ) {
        return true;
    }
    return false;
}
// error

上面获取animal.swim的时候报错

此时可以使用类型断言,将animal断言成Fish

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时报错的问题

需要注意的是,类型断言只能欺骗编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误

使用类型断言尽量避免断言后调用方法或引用深层属性,以减少不必要的运行时错误

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

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来判断ApiError是不是属于Error的实例

但是有时候 ApiError和HttpError不是一个真正的类,而是一个typescript的接口,接口是一个类型不是一个真正的值,编译结果中会删除,当然无法使用instanceof来运行时判断

将任何一个类型断言为any

当我们引用一个在此类型上不存在的属性或方法就会报错

const foo:number = 1;
foo.length = 1; // error

有的时候

window.foo = 1; //error

我们需要将window上添加一个属性foo,但是typescript编译时会报错,提示我们window上不存在foo属性

此时我们可以用 as any 临时将window断言为any类型

(window as any).foo = 1;

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

将一个变量断言为any可以说是解决typescript中国类型问题的最后一个手段

它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any

将any断言为一个具体类型

假如历史遗留代码有个 getCacheData

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

// 断言成一个精确的类型
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();

类型断言的限制

从上面的例子可以总结出:

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

并不是任何一个类型都可以被断言为任何另一个类型

若A兼容B,那么就可以互相断言

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

let tom: Cat = {
    name: 'Tom',
    run: () => { console.log('run') }
};
let animal: Animal = tom;

上面例子中,Cat包含了Animal中所有属性,除此之外它还有一个额外方法run,typescript并不关心他们之间是什么关系,而只会看它们最终结构有什么

等价于:

interface Animal {
    name:string;
}
interface Cat extends Animal {
    run():void;
}

Cat类型的tom可以赋值给Animal类型的animal

双重断言

interface Cat {
    run(): void;
}
interface Fish {
    swim(): void;
}

function testCat(cat: Cat) {
    return (cat as any as Fish);
}

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

类型断言 vs 类型转换

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

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

// 编译为
function toBoolean(something) {
    return something;
}

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

// 若要进行类型转换,需要直接调用类型转换方法
function toBoolean(something: any): boolean {
    return Boolean(something);
}

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

类型转换 vs 类型声明

除了将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();

这和类型断言非常相似,产生的结果也是几乎一样,它们的区别:

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

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}

const animal: Animal = {
    name: 'tom'
};
let tom: Cat = animal;
// error

// Animal可以看做是Cat的父类,当然不能将父类实例赋值给类型为子类的变量

核心区别

  • animal断言为Cat,只需要满足Animal兼容Cat或Cat兼容Animal即可
  • animal赋值给tom需要满足Cat兼容Animal才行
// 所以
const tom = getCacheData('tom') as Cat;
// 等价于
const tom: Cat = getCacheData('tom');

类型断言 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返回值的约束,同时去除掉了代码中的any,是最优的一个解决方案