类型断言

96 阅读5分钟
  • 语法

值 as 类型

  • 类型断言的用途
  1. 将联合类型断言为其中一个类型

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

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

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

有时候需要在还不确定类型的时候就访问其中一个类型特有的属性或方法,譬如:

function isFish(animal: Cat|Fish){
    if (typeOf animal.swim === 'function') return true
    return false
}
//报错,因为swim不是Cat和Fish的共有函数

此时可以使用类型断言,将animal断言成Fish这样就可以解决报错问题

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 Cat).run()
}
const tom:Fish = {
    namr: 'tom',
    swim(){console.log('swim')}
}
swim(tom)
//这个例子编译会通过但是运行不会通过

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

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
}

3.将任何一种类型断言为any

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

const foo:number = 1
foo.length = 1 //number无length属性

但有的时候,可以确定这段代码不会出错

window.foo = 1
//在window上添加一个属性foo,但TypeScript编译时会报错会提示window上不存在foo属性

此时可以使用as any临时将window断言为any类型:

(window as any).foo = 1
//在any类型变量上,访问任何属性都是允许的

注意:将一个变量断言为any是解决TypeScript中类型问题的最后一个手段。它极有可能掩盖了真正的错误,所以如果不是非常确定就不要用as any。 4. 将any断言为一个具体的类型

比如历史遗留的代码中有个getCacheData,它的返回值是any。那么在使用它的时候最好能够调用了它之后的返回值断言成一个精确的类型,这样就方便了后续的操作:

function getCacheData(key: string): any {
    return (window as any).cache[key]
}
interface Cat{
    name: string;
    run(): void
}
const tom = getCacheData('tom') as Cat //将返回的值断言为Cat
tom.run() //再访问tom的时候就有了代码补全,提高了代码的可维护性

5.类型断言的限制

  • 联合类型可以被断言为其中一个类型
  • 父类可以被断言为子类
  • 任何类型都可以被断言为any
  • any可以被断言为任何类型
  • 要使得A能够被断言为B,只需要A兼容B或B兼容A即可
  1. 双重断言

既然,任何类型都可以被断言为any,any可以被断言为任何类型,那么我们是不是可以使用双重断言:as any as Foo来将任何一个类型断言为任何另一个类型?

interface Cat {
    run(): void
}
interface Fish {
    swim(): void
}
function tesrCat(cat: Cat) {
    return (cat as any as Fish)
}
//直接使用cat as Fish肯定会报错,因为Cat和Fish互相不兼容
//但如果使用双重断言又和“要使得A能够被断言为B,只需要A兼容B或B兼容A”不符,所以如果使用了双重断言大概率是非常错误的可能会导致运行错误,除非迫不得已千万别使用双重断言。
  1. 类型断言vs类型转换

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

function toBoolean(something: any): boolean {
    return something as boolean
}
//这段代码会通过编译但并没有什么用
toBoolean(1) //返回值1

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

function toBoolean(something:any): boolean{
    return Boolean(something)
}
toBoolean(1) //返回true
  1. 类型断言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()

它们的区别,可以通过这个例子理解:

interface Animal {
    name: string
}
interface Cat {
    name: string;
    run(): void
}
const animal: Animal = {
    name: 'tom
}
const 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
}
const tom: Cat = animal
//编译报错,不允许将animal赋值给Cat类型的tom

它们的核心区别在于:

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

类型声明是比类型断言更加严格的

  1. 类型断言vs泛型

还是上面的例子,第三种方法解决

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()