TypeScript 聯集與交集型別:用集合思維理解 | 與 & 的差異
用兩個具體的集合建立心智模型
前面已經零星看過幾次「或」的用法,這一節要正式深入聯集(union)與交集(intersection)型別,並延續一路以來把型別當成「允許值的集合」這個心智模型,用實際能驗證的例子建立直覺。
先建立兩個集合:一個是十以內的偶數,另一個是 1 到 5 的數字。畫成文氏圖來看,2 跟 4 同時落在兩個圓圈的交集裡;「1 到 5」這個集合裡的 1、3、5 不是偶數,只落在自己的圓圈;而「偶數」集合裡的 6、8 都大於 5,也只落在自己的圓圈裡,兩邊各自都有一部分是對方沒有的。
聯集型別:型別世界裡的「或」
聯集型別對應到型別世界裡的「或」,TypeScript 用直式符號 | 表示:
ts
type OneThroughFive = 1 | 2 | 3 | 4 | 5;
type Evens = 2 | 4 | 6 | 8;
type OneThroughFiveOrEvens = OneThroughFive | Evens;
// { 1, 2, 3, 4, 5, 6, 8 }這個符號的選擇不是隨便挑的,跟 JavaScript 裡表示邏輯「或」的雙直式符號 || 概念上是相關的,比較好記。如果把 OneThroughFive | Evens 這個集合想成一道門,站在門口的守門人只要值符合任何一邊的集合就會放行:只要是 1 到 5 之間,就能進去;只要是偶數,也能進去。結果就是把兩個集合的內容合併起來,1、2、3、4、5、6、8 全都在這個聯集型別裡,可以想像成先把第一個集合的所有值都收進來,再逐一檢查第二個集合裡的值,如果還沒收錄過就一併加進去,很像一個迴圈在背後跑過一輪。
兩種思考角度:能接受什麼,跟能保證什麼
理解聯集與交集型別時,有兩個角度值得分開思考:這個型別願意接受哪些值,以及對於集合裡的每一個成員,能確定保證什麼。
以剛剛的聯集型別為例,它能接受兩個集合裡的任何一個值,接受範圍很寬。但反過來看能保證什麼:如果有一個函式只想要偶數,這個聯集型別的值不能直接拿去用,因為裡面可能混著 1 這種不是偶數的值;同樣地,如果函式只想要 1 到 5 之間的數字,也不能放心使用這個聯集型別,因為裡面可能是 6 或 8。這說明聯集型別雖然接受的值範圍變大了,但反而能對每個成員做出的保證變少了。
交集型別:型別世界裡的「且」
交集型別剛好相反,對應型別世界裡的「且」,TypeScript 用 & 符號表示,這個選擇同樣呼應了 JavaScript 邏輯運算子裡表示「且」的符號:
ts
type EvenAndOneThroughFive = Evens & OneThroughFive;
// { 2, 4 }用同樣的兩個集合來看,交集型別得到的只有 2 跟 4,也就是兩個集合共同擁有的值。這裡容易搞混的地方在於:講到「且」的時候,直覺上很容易誤以為是把兩個集合的內容全部加在一起,但其實交集型別問的是「這個值是否同時屬於這個集合,也屬於另一個集合」,只有真正兩邊都符合的值才能通過。可以想像成連續站著兩個守門人,任何一個值都得先後通過兩關審核,只要有一關不符合就會被擋下來,這也是為什麼交集型別最終產生的集合,範圍反而比原本任何一個集合都要小。
交集型別:範圍變窄,但保證變多
用前面「能接受什麼」跟「能保證什麼」這兩個角度重新檢視交集型別,會發現它跟聯集型別呈現完全相反的特性。就「能接受什麼值」來說,交集型別非常嚴格,一個值必須同時滿足兩邊的要求才能進來。但正因為這份嚴格,「能保證什麼」反而變得更豐富:如果有一個函式只想要偶數,交集型別裡的 2、4 都能直接拿去用,因為它們保證同時是偶數;如果另一個函式只想要 1 到 5 之間的數字,這個交集型別一樣能滿足,因為 2、4 也同時落在這個範圍內。換句話說,交集型別讓一個值同時符合多個約束條件的好處,是聯集型別做不到的。
這正是理解聯集與交集型別最核心的一組對照:聯集型別接受的值範圍變大,但對每個成員能做的保證變少;交集型別接受的值範圍變小,但對每個成員能做的保證反而變多。
複習
TypeScript 用什麼符號表示聯集型別與交集型別?
聯集型別用直式符號 | 表示,交集型別用 & 符號表示,這兩個符號的選擇都呼應了 JavaScript 裡表示邏輯「或」(||)與「且」(&&)的運算子,方便記憶。
思考聯集與交集型別時,有哪兩個互補的角度?
一個角度是「這個型別願意接受哪些值」,另一個角度是「對於集合裡的每一個成員,能確定保證什麼」。聯集型別接受的值範圍較大,但能做的保證較少;交集型別接受的值範圍較小,但能做的保證較多。
把「1 到 5」跟「十以內偶數」這兩個集合做交集,會得到什麼結果?
只會得到 2 跟 4,也就是同時屬於兩個集合的值,因為交集型別要求一個值必須同時滿足兩邊的條件才能通過。
把「1 到 5」跟「十以內偶數」這兩個集合做聯集,會得到什麼結果?
會得到 {1, 2, 3, 4, 5, 6, 8},也就是把兩個集合的所有值合併起來,只要屬於任何一邊就會被納入聯集型別。
小測驗
TypeScript 用什麼符號表示聯集型別?
`|`交集型別的特徵是什麼?
只包含同時屬於兩個集合的值給定「十以內偶數」(2、4、6、8)與「1 到 5」(1、2、3、4、5)這兩個集合,它們的聯集型別會包含哪些值?
1、2、3、4、5、6、8此文章是 FrontendMasters 上的 TypeScript Fundamentals 課程筆記
