跳至主要內容

交集

有時建立一個類型是有用的,這個類型是所有其他類型的集合。例如,您可能想要撰寫一個函式,它接受一個實作兩個不同介面的值

1interface Serializable {2  serialize(): string;3}4
5interface HasLength {6  length: number;7}8
9function func(value: Serializable & HasLength) {10  // ...11}12
13func({14  length: 3,15  serialize() {16    return '3';17  },18}); // Works19
20func({length: 3}); // Error! Doesn't implement both interfaces
20:6-20:16: Cannot call `func` with object literal bound to `value` because property `serialize` is missing in object literal [1] but exists in `Serializable` [2]. [prop-missing]

交集類型語法

交集類型是任何數量的類型,它們由一個與符號 & 連接。

Type1 & Type2 & ... & TypeN

您也可以新增一個前導的&符號,這在將交集類型拆分到多行時很有用。

type Foo =
& Type1
& Type2
& ...
& TypeN

交集類型中的每個成員可以是任何類型,甚至是另一個交集類型。

type Foo = Type1 & Type2;
type Bar = Type3 & Type4;

type Baz = Foo & Bar;

交集類型需要全部輸入,但只有一個輸出

交集類型與聯集類型相反。在呼叫接受交集類型的函式時,我們必須傳入所有那些類型。但在我們的函式內部,我們只需要將其視為任何一個類型即可。

1type A = {a: number, ...};2type B = {b: boolean, ...};3type C = {c: string, ...};4
5function func(value: A & B & C) {6  const a: A = value;7  const b: B = value;8  const c: C = value;9}

即使我們將值視為其中一種類型,我們也不會收到錯誤,因為它滿足了所有類型。

函式類型的交集

交集類型的常見用途是表達根據我們傳入的輸入回傳不同結果的函式。例如,假設我們要撰寫函式的類型,該函式

  • 在我們傳入值 "string" 時回傳字串,
  • 在我們傳入值 "number" 時回傳數字,以及
  • 在我們傳入任何其他字串時回傳任何可能的類型 (mixed)。

此函式的類型將為

1type Fn =2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);

上述定義中的每一行稱為重載,我們說類型為 Fn 的函式是重載的

請注意箭號類型周圍的括號使用。這些括號對於覆寫「箭號」建構函式在交集上的優先順序是必要的。

呼叫重載函式

使用上述定義,我們可以宣告一個具有以下行為的函式 fn

1declare const fn:2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);5
6const s: string = fn("string"); // Works7const n: number = fn("number"); // Works8const b: boolean = fn("boolean"); // Error!
8:20-8:32: Cannot assign `fn(...)` to `b` because mixed [1] is incompatible with boolean [2]. [incompatible-type]

Flow 透過將參數的類型與具有相容參數類型的第一個重載進行比對來達成此行為。例如,請注意參數 "string" 與第一個和最後一個重載都相符。Flow 只會選擇第一個。如果沒有重載相符,Flow 會在呼叫位置引發錯誤。

宣告重載函式

宣告相同函式 fn 的等效方式是使用連續的「宣告函式」陳述式

1declare function fn(x: "string"): string;2declare function fn(x: "number"): number;3declare function fn(x: string): mixed;

Flow 的一個限制是,它無法根據交叉類型檢查函數主體。換句話說,如果我們在上述宣告之後為 fn 提供以下實作

1function fn(x: mixed) {2  if (x === "string") { return ""; }3  else if (x === "number") { return 0; }4  else { return null; }5}

Flow 會默默接受它(並使用 Fn 作為推斷類型),但不會根據此簽章檢查實作。這使得此類宣告更適合於遺漏實作的函式庫定義

物件類型的交叉

當您建立不精確物件類型的交叉時,表示您的物件滿足交叉的每個成員。

例如,當您建立兩個具有不同屬性集的不精確物件的交叉時,將會產生一個具有所有屬性的物件。

1type One = {foo: number, ...};2type Two = {bar: boolean, ...};3
4type Both = One & Two;5
6const value: Both = {7  foo: 1,8  bar: true9};

當您有屬性因具有相同名稱而重疊時,Flow 會遵循與過載函數相同的策略:它會傳回與此名稱相符的第一個屬性的類型。

例如,如果您合併兩個不精確物件,其中一個名為 prop 的屬性類型為 number,另一個類型為 boolean,則存取 prop 會傳回 number

1type One = {prop: number, ...};2type Two = {prop: boolean, ...};3
4declare const both: One & Two;5
6const prop1: number = both.prop; // Works7const prop2: boolean = both.prop; // Error!
7:24-7:32: Cannot assign `both.prop` to `prop2` because number [1] is incompatible with boolean [2]. [incompatible-type]

若要合併精確物件類型,您應該改用物件類型擴散

1type One = {foo: number};2type Two = {bar: boolean};3
4type Both = {5  ...One,6  ...Two,7};8
9const value: Both = {10  foo: 1,11  bar: true12};

注意:關於物件,Flow 中實作交叉類型的特定順序方式,從集合論的觀點來看,通常看似違反直覺。在集合中,交叉運算元可以任意變更順序(交換性質)。因此,使用物件類型擴散來定義此類運算,會是較佳的作法,因為物件類型擴散更明確地指定排序語意。

不可能的交叉類型

使用交叉類型,可以建立在執行階段無法建立的類型。交叉類型允許您組合任何類型的集合,即使它們彼此衝突。

例如,您可以建立數字和字串的交叉。

1type NumberAndString = number & string;2
3function func(value: NumberAndString) { /* ... */ }4
5func(3.14); // Error!
6func('hi'); // Error!
5:6-5:9: Cannot call `func` with `3.14` bound to `value` because number [1] is incompatible with string [2]. [incompatible-call]
6:6-6:9: Cannot call `func` with `'hi'` bound to `value` because string [1] is incompatible with number [2]. [incompatible-call]

但是您不可能建立同時是數字和字串的值,但您可以為它建立一個類型。建立此類型的類型沒有實際用途,但這是交叉類型運作方式的副作用。

建立不可能類型的意外方式是建立精確物件類型的交集。例如

1function func(obj: {a: number} & {b: string}) { /* ... */ }2
3func({a: 1}); // Error!
4func({b: 'hi'}); // Error!
5func({a: 1, b: 'hi'}); // Error!
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]

物件不可能同時精確地具有屬性 a 和沒有其他屬性,以及精確地具有屬性 b 和沒有其他屬性。