类型断言
第二段路时,已经提到联合类型:变量只能访问联合类型中所有类型共有的属性或方法
语法:值 as 类型
或 <类型>值
用途
将联合类型断言成其中的具体类型
interface IFishman {
// 摸鱼人
name: string,
play(): void
}
interface IWorker {
// 干饭人
name: string
eat(): void
}
function myFunc(person: IFishman | IWorker) {
console.log(person.name)
person.play() // 报错:类型“IFishman | IWorker”上不存在属性“play”。类型“IWorker”上不存在属性“play”。
}
但是,有时候我们就是需要访问非公有的属性或方法。比如上面的例子中,当是Fishman
时,调用play
方法,当是Worker
时,调用eat
方法。
这时候,断言就能用来将联合类型断言成其中的具体类型。
interface IFishman {
// 摸鱼人
name: string,
play(): void
}
interface IWorker {
// 干饭人
name: string,
eat(): void
}
function myFunc(person: IFishman | IWorker) {
(person as IFishman).play() // 将person断言成IFishman
}
const fishman: IFishman = {
name: 'clz',
play: function () {
console.log('摸鱼')
}
}
myFunc(fishman) // 摸鱼
乍一看,挺好的,但是,实际上只是隐藏其他情况而已,比如上面person as IFishman
隐藏了person
为IWorker
的情况,这时候如果传入的参数是IWorker
类型的,那就会报错,而且没法在编译阶段就暴露错误
const worker: IWorker = {
name: 'clz',
eat: function () {
console.log('干饭')
}
}
myFunc(worker)
所以,使用断言时,应该非常注意,不然会增加一些运行时错误
将父类断言成更具体的子类
更准确来说,是将父类型断言成更具体的子类型,因为类的话,使用instanceof
来判断就足够了。
但是,如果我们使用接口的话,它并不是类,而是类型,自然就不能使用instanceof
来判断,这时候就需要使用断言来将父类型断言成更具体的子类型(实际上和将联合类型断言成其中的具体类型很像)
class Person {
name: string
}
interface IFishman extends Person {
// 摸鱼人
play(): void
}
interface IWorker extends Person {
// 干饭人
eat(): void
}
function myFunc(person: Person) {
// console.log(person instanceof IFishman) // 会报错:“IFishman”仅表示类型,但在此处却作为值使用。
if (typeof (person as IFishman).play === 'function') {
return '摸鱼人'
} else if (typeof (person as IWorker).eat === 'function') {
return '干饭人'
}
}
const worker: IWorker = {
name: 'clz',
eat: function () { }
}
console.log(myFunc(worker)) // 干饭人
将任何一个类型断言成any
我们使用JS进行开发时,有时候可以在window
对象上添加新的属性,这个属性就能够全局访问了,但是,在TS中是会报错的,因为window
对象没有该属性,就会报错。
但是,这个做法实际上在开发中能够很便利,这个时候可以使用断言将它断言成any
类型,这样子就能够添加新属性了。
(window as any).a = 123
需要注意的是,这样可能会掩盖真正的类型错误
将any
断言成具体类型
设想一个情境,一个获取两个参数的和的函数,返回值按理应该是number
类型,但是,结果却是any
类型,这样子就会导致很多能在编译阶段暴露出的错误没法暴露出来。(这个情境是随便想的,简单来讲就是,历史代码不太好动,可能会引发蝴蝶效应)
function mySum(a: number, b: number): any {
return a + b
}
const sum = mySum(9, 8)
console.log(sum.length)
比如上面,我们应该在访问sum.length
时报错才对,但是因为是任意类型,所以不会报错,所以这时候就可能使用断言,将any
断言成具体的类型,恢复它的在编译阶段报错的功能。
断言规则
如果A兼容B或者B兼容A,那么A能够被断言为B。
这里的兼容简单来说就是:A兼容B就是指类型A是类型B的子集。
class Person {
name: string
}
interface IFishman extends Person {
// 摸鱼人
play(): void
}
function testPerson(person: Person) {
return (person as IFishman)
}
function testIFishman(fishman: IFishman) {
return (fishman as Person)
}
上面的例子中,类型Person
是类型IFishman
的子集,即Person
兼容IFishman
,所以Person
能被断言为IFishman
,IFishman
也能被断言为Person
。
上面使用的Person
是类,而IFishman
是继承了Person
,所以可能会误以为是继承关系导致的能否被断言。实际上,断言并不是根据是否有继承关系,而是看有没有兼容关系。所以下面的做法也是可以的。
interface IPerson {
name: string
}
interface IFishman {
// 摸鱼人
name: string,
play(): void
}
function testPerson(person: IPerson) {
return (person as IFishman)
}
function testIFishman(fishman: IFishman) {
return (fishman as IPerson)
}
如果类型A不兼容B,并且类型B不兼容A,那么A不能断言为B,B也不能断言为A。
比如number
和string
。
双重断言
- 任何类型都可以被断言成
any
any
可以被断言成任何类型
所以,可以使用双重断言把任何一个类型断言成任何另一个类型。
function mytest(num: number) {
return (num as any as string)
}
类型断言不会进行类型转换
类型断言只在TS编译时有效果,在编译结果中会被删除,不会影响到编译结果的类型。
这个例子中,乍一看,断言还同时实现了类型的转换。
但是,都是假象。编译结果,立马打回原形。
所以,需要进行类型转换还是得老老实实直接调用类型转换的方法。
function mytest(num: number) {
return String(num)
}
const num: number = 1
const str = mytest(num)
console.log(str)
console.log(typeof str)
类型断言VS类型声明
function mytest(num: number): any {
return num
}
const num = mytest(123) as number
console.log(typeof num)
我们可以使用断言将any
类型断言为number
类型。
当然我们也可以使用类型声明的方式来实现。
const num: number = mytest(123)
这么一看,结果几乎是一样的。
实际上,类型声明的使用会比类型断言要更严格,所以使用类型断言很可能会导致一些隐藏问题。
先来看看,类型断言和类型声明的核心区别:
- A断言为B:需要满足A兼容B,或者B兼容A
- A赋值给B:只有满足B兼容A才行
interface IPerson {
name: string
}
interface IFishman {
// 摸鱼人
name: string,
play(): void
}
const person: IPerson = {
name: 'clz'
}
const fishman = person as IFishman
上面的断言:person
兼容IFishman
,所以IPerson
类型能断言为IFishman
类型,不过会有一些隐藏问题,比如IFishman
类型原本是需要有play
方法的,但是用断言就直接出现了一个没有play
方法的IFishman
。
使用类型声明会更严格,但是也能够避免一些隐藏错误。
const fishman: IFishman = person