攀爬TS之路(五) 类型断言
类型断言
第二段路时,已经提到联合类型:变量只能访问联合类型中所有类型共有的属性或方法
语法:值 as 类型
或 <类型>值
用途
将联合类型断言成其中的具体类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface IFishman { name: string, play(): void }
interface IWorker { name: string eat(): void }
function myFunc(person: IFishman | IWorker) { console.log(person.name)
person.play() }
|
但是,有时候我们就是需要访问非公有的属性或方法。比如上面的例子中,当是Fishman
时,调用play
方法,当是Worker
时,调用eat
方法。
这时候,断言就能用来将联合类型断言成其中的具体类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| interface IFishman { name: string, play(): void }
interface IWorker { name: string, eat(): void }
function myFunc(person: IFishman | IWorker) { (person as IFishman).play() }
const fishman: IFishman = { name: 'clz', play: function () { console.log('摸鱼') } }
myFunc(fishman)
|
乍一看,挺好的,但是,实际上只是隐藏其他情况而已,比如上面person as IFishman
隐藏了person
为IWorker
的情况,这时候如果传入的参数是IWorker
类型的,那就会报错,而且没法在编译阶段就暴露错误
1 2 3 4 5 6 7 8
| const worker: IWorker = { name: 'clz', eat: function () { console.log('干饭') } }
myFunc(worker)
|
所以,使用断言时,应该非常注意,不然会增加一些运行时错误
将父类断言成更具体的子类
更准确来说,是将父类型断言成更具体的子类型,因为类的话,使用instanceof
来判断就足够了。
但是,如果我们使用接口的话,它并不是类,而是类型,自然就不能使用instanceof
来判断,这时候就需要使用断言来将父类型断言成更具体的子类型(实际上和将联合类型断言成其中的具体类型很像)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Person { name: string }
interface IFishman extends Person { play(): void }
interface IWorker extends Person { eat(): void }
function myFunc(person: Person) {
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
类型,这样子就能够添加新属性了。
需要注意的是,这样可能会掩盖真正的类型错误
将any
断言成具体类型
设想一个情境,一个获取两个参数的和的函数,返回值按理应该是number
类型,但是,结果却是any
类型,这样子就会导致很多能在编译阶段暴露出的错误没法暴露出来。(这个情境是随便想的,简单来讲就是,历史代码不太好动,可能会引发蝴蝶效应)
1 2 3 4 5 6
| 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的子集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
,所以可能会误以为是继承关系导致的能否被断言。实际上,断言并不是根据是否有继承关系,而是看有没有兼容关系。所以下面的做法也是可以的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
可以被断言成任何类型
所以,可以使用禁术双重断言把任何一个类型断言成任何另一个类型。
1 2 3
| function mytest(num: number) { return (num as any as string) }
|
除非迫不得已,千万别用双重断言。禁术:伤敌一千,自损八百
类型断言不会进行类型转换
类型断言只在TS编译时有效果,在编译结果中会被删除,不会影响到编译结果的类型。
这个例子中,乍一看,断言还同时实现了类型的转换。
但是,都是假象。编译结果,立马打回原形。
所以,需要进行类型转换还是得老老实实直接调用类型转换的方法。
1 2 3 4 5 6 7 8 9 10
| function mytest(num: number) { return String(num) }
const num: number = 1 const str = mytest(num)
console.log(str) console.log(typeof str)
|
类型断言VS类型声明
1 2 3 4 5 6
| function mytest(num: number): any { return num }
const num = mytest(123) as number console.log(typeof num)
|
我们可以使用断言将any
类型断言为number
类型。
当然我们也可以使用类型声明的方式来实现。
1
| const num: number = mytest(123)
|
这么一看,结果几乎是一样的。
实际上,类型声明的使用会比类型断言要更严格,所以使用类型断言很可能会导致一些隐藏问题。
先来看看,类型断言和类型声明的核心区别:
- A断言为B:需要满足A兼容B,或者B兼容A
- A赋值给B:只有满足B兼容A才行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
。
使用类型声明会更严格,但是也能够避免一些隐藏错误。
1
| const fishman: IFishman = person
|