跳至主要內容

聯合

有時,建立一個類型很有用,這個類型是其他類型集合中的其中一個。例如,您可能想要撰寫一個函式,它接受一組基本值類型。對於此 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 列舉,它們可能是聯合 字面值類型 的替代方案。

聯合速記

某個類型 Tnullvoid 的聯合很常見,因此我們提供一個稱為 可能類型 的速記,方法是使用 ? 前綴。類型 ?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 中的聯集類型,您可以將值 精煉 為單一類型。

例如,如果我們有一個值,其聯集類型為 numberbooleanstring,我們可以使用 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}

使用精確物件型別時,我們無法有額外的屬性,因此物件會彼此衝突,我們就能夠區分它們。