跳至主要內容

子集和子類型

什麼是子類型?

numberbooleanstring 這樣的類型描述了一組可能的值。number 描述了所有可能存在的數字,因此單一數字(例如 42)將會是 number 類型的子類型。相反地,number 將會是 42 類型的超類型

如果我們想要知道某個類型是否為另一個類型的子類型,我們需要檢視這兩個類型的所有可能值,並找出其中一個類型是否包含另一個類型的子集

例如,如果我們有一個 TypeA 描述了數字 1 到 3(一個聯集文字類型),以及一個 TypeB 描述了數字 1 到 5:TypeA 將會被視為 TypeB子類型,因為 TypeATypeB 的子集。

1type TypeA = 1 | 2 | 3;2type TypeB = 1 | 2 | 3 | 4 | 5;

考慮一個 TypeLetters 描述了字串:「A」、「B」、「C」,以及一個 TypeNumbers 描述了數字:1、2、3。它們都不會是彼此的子類型,因為它們各自包含了完全不同的值組。

1type TypeLetters = "A" | "B" | "C";2type TypeNumbers =  1  |  2  |  3;

最後,如果我們有一個描述數字 1 到 3 的 TypeA,以及一個描述數字 3 到 5 的 TypeB。它們都不會是彼此的子類型。儘管它們都有 3 並且描述數字,但它們各自都有一些唯一的項目。

1type TypeA = 1 | 2 | 3;2type TypeB = 3 | 4 | 5;

什麼時候使用子類型?

Flow 所做的工作大部分是將類型相互比較。

例如,為了知道您是否正確呼叫函式,Flow 需要將您傳遞的引數與函式預期的參數進行比較。

這通常表示找出您傳入的值是否為您預期的值的子類型。

因此,如果您編寫一個預期數字 1 到 5 的函式,則該集合的任何子類型都將被接受。

1function f(param: 1 | 2 | 3 | 4 | 5) {2  // ...3}4
5declare const oneOrTwo: 1 |  2; // Subset of the input parameters type.6declare const fiveOrSix: 5 | 6; // Not a subset of the input parameters type.7
8f(oneOrTwo); // Works!9f(fiveOrSix); // Error!
9:3-9:11: Cannot call `f` with `fiveOrSix` bound to `param` because number literal `6` [1] is incompatible with literal union [2]. [incompatible-call]

複雜類型的子類型

Flow 不僅需要比較原始值的集合,還需要能夠比較物件、函式以及語言中出現的每一個其他類型。

物件的子類型

您可以透過其鍵來開始比較兩個物件。如果一個物件包含另一個物件的所有鍵,則它可能是子類型。

例如,如果我們有一個包含鍵 fooObjectA,以及一個包含鍵 foobarObjectB。那麼,如果 ObjectA 不精確,則 ObjectB 可能為 ObjectA 的子類型。

1type ObjectA = {foo: string, ...};2type ObjectB = {foo: string, bar: number};3
4let objectB: ObjectB = {foo: 'test', bar: 42};5let objectA: ObjectA = objectB; // Works!

但是,我們也需要比較值的類型。如果兩個物件都有一個鍵 foo,但一個是 number 而另一個是 string,則一個不會是另一個的子類型。

1type ObjectA = {foo: string, ...};2type ObjectB = {foo: number, bar: number};3
4let objectB: ObjectB = { foo: 1, bar: 2 };5let objectA: ObjectA = objectB; // Error!
5:24-5:30: Cannot assign `objectB` to `objectA` because number [1] is incompatible with string [2] in property `foo`. [incompatible-type]

如果物件上的這些值碰巧是其他物件,我們必須將它們相互比較。我們需要遞迴地比較每個值,直到我們可以決定我們是否有子類型。

函式的子類型

函數的子類型規則較為複雜。到目前為止,我們已看到如果 B 包含 A 的所有可能值,則 AB 的子類型。對於函數來說,這種關係如何套用並不清楚。為簡化起見,你可以將函數類型 A 視為函數類型 B 的子類型,如果類型 A 的函數可以在任何預期類型 B 的函數的地方使用。

假設我們有一個函數類型和幾個函數。哪些函數可以在預期給定函數類型的程式碼中安全使用?

1type FuncType = (1 | 2) => "A" | "B";2
3declare function f1(1 | 2): "A" | "B" | "C";4declare function f2(1 | null): "A" | "B";5declare function f3(1 | 2 | 3): "A";6
7f1 as FuncType; // Error
8f2 as FuncType; // Error
9f3 as FuncType; // Works!
7:1-7:2: Cannot cast `f1` to `FuncType` because string literal `C` [1] is incompatible with literal union [2] in the return value. [incompatible-cast]
8:1-8:2: Cannot cast `f2` to `FuncType` because literal union [1] is incompatible with number literal `2` [2] in the first parameter. [incompatible-cast]
  • f1 可以傳回 FuncType 永遠不會傳回的值,因此如果使用 f1,依賴 FuncType 的程式碼可能不安全。它的類型不是 FuncType 的子類型。
  • f2 無法處理 FuncType 處理的所有參數值,因此依賴 FuncType 的程式碼無法安全使用 f2。它的類型也不是 FuncType 的子類型。
  • f3 可以接受 FuncType 處理的所有參數值,而且只傳回 FuncType 處理的值,因此它的類型是 FuncType 的子類型。

一般來說,函數子類型規則如下:函數類型 B 是函數類型 A 的子類型,當且僅當 B 的輸入是 A 的超集,而且 B 的輸出是 A 的子集。子類型必須接受與其父類型至少相同的輸入,而且必須傳回最多相同的輸出。

在輸入和輸出上套用子類型規則的方向決定是由變異控制,這是下一節的主題。