TypeScript 联合类型(union type)
TS是JS的超集,在JS的基礎(chǔ)上添加了一套類型系統(tǒng),這樣的TS可以被靜態(tài)分析帶來的好處顯而易見。
let val: string = 'val';聲明一個string類型的變量val。
let val: string = 'val'; val = 1; // Type 'number' is not assignable to type 'string'.因為number類型和string類型并不兼容,在string類型值出現(xiàn)的地方并不能使用number類型指完成替換,所以在TS世界中給string類型的val變量賦值number類型的值會報錯。
但是在JS中并沒有賦值的限制:
// javascript var val = 'val'; b = 1; // 這里不會報錯在JS中變量val首先被賦值了字符串,然后被賦值了數(shù)字,這兩個數(shù)據(jù)類型并不一致,但是因為JS沒有靜態(tài)類型檢查,所以這并不會報錯。
TS的聯(lián)合類型可以適應(yīng)這種情況,表示這個變量可能是類型a也可能是類型b也可能是類型c等類型。
let val: string | number = 'val' val = 1 // 這里并不會報錯在TS中,這里聲明的變量val則表示可能是類型string也可能是類型number,所以對變量賦值string類型值和number類型值,并不會報錯。
但問題也隨之而來。
function test(val: string | number) {val.toLowerCase() // Property 'toLowerCase' does not exist on type 'string | number'. }TS會提示類型 string | number 上沒有屬性toLowserCase。
這個報錯也很容易理解,因為類型string的數(shù)據(jù)上可以調(diào)用方法toLowerCase,但是number不可以。因為變量val的類型,string和number都有可能,TS并不能確定val在運行時是string類型,所以會出現(xiàn)這個錯誤。
我們需要一個方法來告訴TS這個變量現(xiàn)在是string類型。
使用typeof縮小類型范圍
對于上面例子中的變量val.toLowerCase的報錯來說是因為val的類型范圍有點大(string | number),如果我們通過某種方式縮小該范圍為string,那么在val上訪問屬性toLowserCase應(yīng)當(dāng)沒有問題。
function test(val: string | number) {if (typeof val === 'string') {val.toLowerCase() // 這里并不會報錯console.log('val是字符串')} else {console.log('val是數(shù)字')} }在TS中,if中的typeof val === 'string'這種形式的代碼會被識別為類型保護(type guard)。TS會分析代碼的執(zhí)行流程,縮小變量可能的類型。在分支if (typeof val === ‘string’) 中變量val的類型被TS識別為’string’,所以在這個分支下val調(diào)用string類型數(shù)據(jù)的方法并不會報錯。
因為val是string和number這兩個類型的聯(lián)合,TS不僅知道if子句中的val是string類型,還知道else中的val是number類型。
可以被typeof進行類型保護的類型有:
- “string”
- “number”
- “bigint”
- “boolean”
- “symbol”
- “undefined”
- “object”
- “function”
使用in縮小類型范圍
上面介紹了對于基礎(chǔ)類型的識別,在TS中使用頻率更多的還有對象,使用typeof來區(qū)別不同對象顯然是有問題的,因為typeof出來的結(jié)果都是’object’無法分辨兩個不同的對象。
type A = {a: string} type B = {b: string}function test(val: A | B) {val.a // Property 'a' does not exist on type 'A | B' }在test函數(shù)中無論是訪問val.a還是val.b都會報錯,而原因已經(jīng)明白,TS無法確定變量val的具體類型,TS并不知道當(dāng)前是類型A還是類型B,所以我們需要幫他一把。
type A = {a: string} type B = {b: string}function test(val: A | B) {if ('a' in val) {console.log(val.a)return}console.log(val.b) }其中的in操作同樣會被TS識別為類型保護,如果屬性a存在于變量val中那么就能識別出val變量是類型A,則可以正常訪問類型A中存在的屬性a。
使用instanceof縮小類型范圍
對于對象類型的區(qū)分除了使用操作符in還可以使用instanceof來完成。
function test(x: Date | string) {if (x instanceof Date) {console.log(x.toUTCString());} else {console.log(x.toUpperCase());} }使用 === 和 == 縮小類型范圍
使用嚴(yán)格等于(===)也可以在某些特別情況下正確縮小類型范圍
function example(x: string | number, y: string | boolean) {if (x === y) {// We can now call any 'string' method on 'x' or 'y'.x.toUpperCase();y.toLowerCase();} else {console.log(x); // x 是 string | numberconsole.log(y); // y 是 string | boolean} }在這個例子中x是string 和number的聯(lián)合,而y是string和boolean的聯(lián)合,當(dāng)x === y的時候只可能x和y都是string類型。所以在分支if (x === y) 中x和y的類型被正確識別為string類型。
我們知道在JS中寬松等于(== null)可以匹配null和undefined兩種類型,當(dāng)然TS也知道,所以 ==null,可以被用來識別類型。
interface Container {value: number | null | undefined; }function multiplyValue(container: Container, factor: number) {// Remove both 'null' and 'undefined' from the type.if (container.value != null) {console.log(container.value);// Now we can safely multiply 'container.value'.container.value *= factor;} }即使container.value可能是null或者undefined類型,但是在分支container.value != null中,該變量類型就只可能是number類型,所以其參與算術(shù)運算并不會報錯。
區(qū)分聯(lián)合類型
interface Circle {kind: "circle";radius: number; }interface Square {kind: "square";sideLength: number; }type Shape = Circle | Square;function getArea(shape: Shape) {if (shape.kind === "circle") {return Math.PI * shape.radius ** 2;} }TS會通過參與聯(lián)合的類型都有的屬性kind來識別當(dāng)前shape是Circle或者Square達到類型保護的目的。
類型謂詞
上面介紹了TS對于聯(lián)合類型中基礎(chǔ)類型和對象類型的類型保護。但是這并不能覆蓋全部的場景,例如上面介紹到的內(nèi)容,并不能區(qū)分兩個函數(shù):
type fn1 = (arg: number) => boolean type fn2 = (arg: string) => booleanfunction test(fn: fn1 | fn2) {return fn(1) }在這個例子里,我們并沒有方法來識別fn是類型fn1還是類型fn2。
在前面的例子里,例如 if (typeof a === ‘string’) 這里面的變量a會被TS類型系統(tǒng)識別為string,如果TS將識別一個變量為某個類型的能力開放給開發(fā)者,上面的問題就會迎刃而解。這個能力就是類型謂詞。
通過觀察上面類型保護的規(guī)律就會發(fā)現(xiàn)TS總會詢問:變量a是類型A嗎?被識別為類型保護的操作的回答總是true或者false都是boolean值。typoef a === ‘string’或者’a’ in A或者a instanceof A,這些操作的返回值都是boolean,并且都是和特定類型作比較。
// 我就是類型謂詞形成的類型保護 function isTypefn1(fn: fn1 | fn2): fn is fn1 {if (fn.name === 'fn1') return truereturn false } function test(fn: fn1 | fn2) {if (isTypefn1(fn)) return fn(1)else return fn('1') }其中 isTypefn1調(diào)用的返回值就會告訴TS入?yún)⑹遣皇穷愋蚮n1,這樣TS就可以識別變量fn的類型完成類型保護。
參考
Narrowing
總結(jié)
以上是生活随笔為你收集整理的TypeScript 联合类型(union type)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网格布局之网格元素放置算法
- 下一篇: TypeScript 交叉类型(inte