聯合
有時,建立一個類型很有用,這個類型是其他類型集合中的其中一個。例如,您可能想要撰寫一個函式,它接受一組基本值類型。對於此 Flow 支援聯合類型。
1function toStringPrimitives(value: number | boolean | string): string {2 return String(value);3}4
5toStringPrimitives(1); // Works!6toStringPrimitives(true); // Works!7toStringPrimitives('three'); // Works!8
9toStringPrimitives({prop: 'val'}); // Error! 10toStringPrimitives([1, 2, 3, 4, 5]); // Error!
9:20-9:32: Cannot call `toStringPrimitives` with object literal bound to `value` because: [incompatible-call] Either object literal [1] is incompatible with number [2]. Or object literal [1] is incompatible with boolean [3]. Or object literal [1] is incompatible with string [4].10:20-10:34: Cannot call `toStringPrimitives` with array literal bound to `value` because: [incompatible-call] Either array literal [1] is incompatible with number [2]. Or array literal [1] is incompatible with boolean [3]. Or array literal [1] is incompatible with string [4].
聯合類型語法
聯合類型是任何數量的類型,它們由垂直線 |
連接。
Type1 | Type2 | ... | TypeN
您也可以新增一個前導垂直線,這在將聯合類型分成多行時很有用。
type Foo =
| Type1
| Type2
| ...
| TypeN
聯合類型的每個成員都可以是任何類型,甚至是另一個聯合類型。
1type Numbers = 1 | 2;2type Colors = 'red' | 'blue'3
4type Fish = Numbers | Colors;
如果您已啟用 Flow 列舉,它們可能是聯合 字面值類型 的替代方案。
聯合速記
某個類型 T
與 null
或 void
的聯合很常見,因此我們提供一個稱為 可能類型 的速記,方法是使用 ?
前綴。類型 ?T
等於 T | null | void
1function maybeString(x: ?string) { /* ... */ }2maybeString('hi'); // Works!3maybeString(null); // Works!4maybeString(undefined); // Works!
所有單一類型形成的聯集為 mixed
類型
1function everything(x: mixed) { /* ... */ }2everything(1); // Works!3everything(true); // Works!4everything(null); // Works!5everything({foo: 1}); // Works!6everything(new Error()); // Works!
聯集與精煉
當您有一個值是聯集類型時,通常會將其分解並分別處理每個單獨類型。使用 Flow 中的聯集類型,您可以將值 精煉 為單一類型。
例如,如果我們有一個值,其聯集類型為 number
、boolean
或 string
,我們可以使用 JavaScript 的 typeof
運算子來分別處理數字案例。
1function toStringPrimitives(value: number | boolean | string) {2 if (typeof value === 'number') {3 return value.toLocaleString([], {maximumSignificantDigits: 3}); // Works!4 }5 // ...6}
透過檢查值的 typeof
並測試它是否為 number
,Flow 會知道在該區塊內它只是一個數字。然後我們可以在該區塊內將我們的值視為數字來撰寫程式碼。
聯集類型需要一個輸入,但所有輸出
在呼叫接受聯集類型的函式時,我們必須傳入其中一種類型。但在函式內部,我們需要處理所有可能的類型。
讓我們使用 精煉個別處理每個類型,並重新撰寫函式。
1function toStringPrimitives(value: number | boolean | string): string {2 if (typeof value === 'number') {3 return String(value);4 } else if (typeof value === 'boolean') {5 return String(value);6 }7 return value; // If we got here, it's a `string`!8}
如果我們沒有處理值的每種類型,Flow 會給我們一個錯誤
1function toStringPrimitives(value: number | boolean | string): string {2 if (typeof value === 'number') {3 return String(value);4 }5 return value; // Error! 6}
5:10-5:14: Cannot return `value` because boolean [1] is incompatible with string [2]. [incompatible-return]
不相交物件聯集
Flow 中有一種特殊的聯集類型稱為「不相交物件聯集」,可用於 精煉。這些不相交物件聯集由任意數量的物件類型組成,每個物件類型都標記有一個單一屬性。
例如,假設我們有一個函式,用於在我們向伺服器發送請求後處理伺服器的回應。當請求成功時,我們會收到一個物件,其 type
屬性設定為 'success'
,以及我們已更新的 value
。
{type: 'success', value: 23}
當請求失敗時,我們會收到一個物件,其 type
設定為 'error'
,以及描述錯誤的 error
屬性。
{type: 'error', error: 'Bad request'}
我們可以嘗試在單一物件類型中表示這兩個物件。但是,我們很快就會遇到問題,因為我們知道屬性會根據 type
屬性存在,但 Flow 不知道。
1type Response = {2 type: 'success' | 'error',3 value?: number,4 error?: string5};6
7function handleResponse(response: Response) {8 if (response.type === 'success') {9 const value: number = response.value; // Error! 10 } else {11 const error: string = response.error; // Error! 12 }13}
9:27-9:40: Cannot assign `response.value` to `value` because undefined [1] is incompatible with number [2]. [incompatible-type]11:27-11:40: Cannot assign `response.error` to `error` because undefined [1] is incompatible with string [2]. [incompatible-type]
嘗試將這兩個單獨的類型組合成單一類型只會造成麻煩。
相反,如果我們建立兩個物件類型的聯集類型,Flow 將能夠根據 type
屬性知道我們正在使用哪個物件。
1type Response =2 | {type: 'success', value: 23}3 | {type: 'error', error: string};4
5function handleResponse(response: Response) {6 if (response.type === 'success') {7 const value: number = response.value; // Works!8 } else {9 const error: string = response.error; // Works!10 }11}
要使用此模式,您的聯合中每個物件都必須有一個鍵 (在我們上面的範例中為 type
),且每個物件都必須為該鍵設定不同的 文字型別 (在我們的範例中為字串 'success'
和字串 'error'
)。您可以使用任何類型的文字型別,包括數字和布林值。
具有精確型別的不相交物件聯合
不相交聯合需要您使用單一屬性來區分每個物件型別。您無法透過不同的屬性來區分兩個不同的 不精確物件。
1type Success = {success: true, value: boolean, ...};2type Failed = {error: true, message: string, ...};3
4function handleResponse(response: Success | Failed) {5 if (response.success) {6 const value: boolean = response.value; // Error! 7 }8}
6:37-6:41: Cannot get `response.value` because property `value` is missing in object type [1]. [prop-missing]
這是因為在 Flow 中,傳遞物件值時,其屬性可以比不精確物件型別預期的多 (因為 寬度子型別)。
1type Success = {success: true, value: boolean, ...};2type Failed = {error: true, message: string, ...};3
4function handleResponse(response: Success | Failed) {5 // ...6}7
8handleResponse({9 success: true,10 error: true,11 value: true,12 message: 'hi'13});
除非物件彼此間有某種衝突,否則無法區分它們。
不過,您可以使用精確物件型別來解決此問題。
1type Success = {success: true, value: boolean};2type Failed = {error: true, message: string};3
4type Response = Success | Failed;5
6function handleResponse(response: Response) {7 if (response.success) {8 const value: boolean = response.value;9 } else {10 const message: string = response.message;11 }12}
使用精確物件型別時,我們無法有額外的屬性,因此物件會彼此衝突,我們就能夠區分它們。