跳至主要內容

泛型

泛型(有時稱為多型類型)是一種抽象類型的方法。

想像一下撰寫下列 identity 函式,它會傳回傳遞的任何值。

function identity(value) {
return value;
}

由於它可以是任何東西,因此我們會遇到很多困難,試圖為此函式撰寫特定類型。

1function identity(value: string): string {2  return value;3}

相反地,我們可以在函式中建立一個泛型(或多型類型),並在其他類型中使用它。

1function identity<T>(value: T): T {2  return value;3}

泛型可用於函式、函式類型、類別、類型別名和介面中。

警告:Flow 不會推論泛型類型。如果您希望某個項目具有泛型類型,請為它加上註解。否則,Flow 可能推論出的類型比您預期的少多型。

泛型的語法

泛型類型會出現在語法中的許多不同地方。

具有泛型的函式

函式可以在函式參數清單之前加入類型參數清單 <T> 來建立泛型。

您可以在函式中新增任何其他類型的位置(參數或回傳類型)使用泛型。

1function method<T>(param: T): T {2  return param;3}4
5const f = function<T>(param: T): T {6  return param;7}

具有泛型的函式類型

函式類型可以透過在函式類型參數清單之前加入類型參數清單 <T> 來建立泛型,方式與一般函式相同。

您可以在函式類型中新增任何其他類型的位置(參數或回傳類型)使用泛型。

<T>(param: T) => T

然後將其用作自己的類型。

1function method(func: <T>(param: T) => T) {2  // ...3}

具有泛型的類別

類別可以在類別主體之前放置類型參數清單來建立泛型。

1class Item<T> {2  // ...3}

您可以在類別中新增任何其他類型的位置(屬性類型和方法參數/回傳類型)使用泛型。

1class Item<T> {2  prop: T;3
4  constructor(param: T) {5    this.prop = param;6  }7
8  method(): T {9    return this.prop;10  }11}

具有泛型的類型別名

1type Item<T> = {2  foo: T,3  bar: T,4};

具有泛型的介面

1interface Item<T> {2  foo: T,3  bar: T,4}

提供可呼叫項目的類型引數

您可以在呼叫中直接為可呼叫實體的泛型提供類型引數

1function doSomething<T>(param: T): T {2  // ...3  return param;4}5
6doSomething<number>(3);

您也可以在 new 表達式中直接為泛型類別提供類型引數

1class GenericClass<T> {}2const c = new GenericClass<number>();

如果您只想指定部分類型引數,可以使用 _ 讓 Flow 為您推論類型

1class GenericClass<T, U=string, V=number>{}2const c = new GenericClass<boolean, _, string>();

警告:基於效能考量,我們建議您在可以時使用具體引數進行註解。_ 並非不安全,但其速度比明確指定類型引數慢。

泛型的行為

泛型就像變數

泛型類型很像變數或函式參數,只不過它們用於類型。您可以在它們在範圍內時使用它們。

1function constant<T>(value: T): () => T {2  return function(): T {3    return value;4  };5}

建立您需要的泛型數量

您可以在類型參數清單中擁有任意數量的這些泛型,並將它們命名為您想要的任何名稱

1function identity<One, Two, Three>(one: One, two: Two, three: Three) {2  // ...3}

泛型追蹤值

當對值使用泛型類型時,Flow 會追蹤該值並確保您不會將其替換為其他東西。

1function identity<T>(value: T): T {2  return "foo"; // Error!
3}4 5function identity<T>(value: T): T {
6 value = "foo"; // Error!
7 return value; // Error!
8}
2:10-2:14: Cannot return `"foo"` because string [1] is incompatible with `T` [2]. [incompatible-return]
5:10-5:17: Cannot declare `identity` [1] because the name is already bound. [name-already-bound]
6:11-6:15: Cannot assign `"foo"` to `value` because string [1] is incompatible with `T` [2]. [incompatible-type]
7:10-7:14: Cannot return `value` because string [1] is incompatible with `T` [2]. [incompatible-return]

Flow 追蹤您透過泛型傳遞的值的特定類型,讓您稍後可以使用它。

1function identity<T>(value: T): T {2  return value;3}4
5let one: 1 = identity(1);6let two: 2 = identity(2);7let three: 3 = identity(42); // Error
7:16-7:27: Cannot assign `identity(...)` to `three` because number [1] is incompatible with number literal `3` [2]. [incompatible-type]

將類型新增至泛型

類似於 mixed,泛型具有「未知」類型。您不能將泛型用作特定類型。

1function logFoo<T>(obj: T): T {2  console.log(obj.foo); // Error!
3 return obj;4}
2:19-2:21: Cannot get `obj.foo` because property `foo` is missing in mixed [1]. [incompatible-use]

您可以精煉類型,但泛型仍允許傳入任何類型。

1function logFoo<T>(obj: T): T {2  if (obj && obj.foo) {3    console.log(obj.foo); // Works.4  }5  return obj;6}7
8logFoo({ foo: 'foo', bar: 'bar' });  // Works.9logFoo({ bar: 'bar' }); // Works. :(

相反地,您可以像使用函式參數一樣,將類型新增至泛型。

1function logFoo<T: {foo: string, ...}>(obj: T): T {2  console.log(obj.foo); // Works!3  return obj;4}5
6logFoo({ foo: 'foo', bar: 'bar' });  // Works!7logFoo({ bar: 'bar' }); // Error!
7:8-7:21: Cannot call `logFoo` because property `foo` is missing in object literal [1] but exists in object type [2] in type argument `T`. [prop-missing]

這樣,您可以保留泛型的行為,同時只允許使用特定類型。

1function identity<T: number>(value: T): T {2  return value;3}4
5let one: 1 = identity(1);6let two: 2 = identity(2);7let three: "three" = identity("three"); // Error!
7:31-7:37: Cannot call `identity` because string [1] is incompatible with number [2] in type argument `T`. [incompatible-call]

泛型類型作為界限

1function identity<T>(val: T): T {2  return val;3}4
5let foo: 'foo' = 'foo';           // Works!6let bar: 'bar' = identity('bar'); // Works!

在 Flow 中,當您將一種類型傳遞到另一種類型時,您通常會失去原始類型。因此,當您將特定類型傳遞到較不具體的類型時,Flow 會「忘記」它曾經是更具體的東西。

1function identity(val: string): string {2  return val;3}4
5let foo: 'foo' = 'foo';           // Works!6let bar: 'bar' = identity('bar'); // Error!
6:18-6:32: Cannot assign `identity(...)` to `bar` because string [1] is incompatible with string literal `bar` [2]. [incompatible-type]

泛型允許您在新增約束的同時保留更具體的類型。這樣,泛型上的類型就充當「界限」。

1function identity<T: string>(val: T): T {2  return val;3}4
5let foo: 'foo' = 'foo';           // Works!6let bar: 'bar' = identity('bar'); // Works!

請注意,當您具有具有約束泛型類型的值時,您不能將其用作更具體的類型。

1function identity<T: string>(val: T): T {2  let str: string = val; // Works!3  let bar: 'bar'  = val; // Error!
4 return val;5}6 7identity('bar');
3:21-3:23: Cannot assign `val` to `bar` because string [1] is incompatible with string literal `bar` [2]. [incompatible-type]

參數化泛型

泛型有時允許您傳入類型,就像函式的引數一樣。這些稱為參數化泛型(或參數多型性)。

例如,具有泛型的類型別名是參數化的。當您使用它時,您必須提供類型引數。

1type Item<T> = {2  prop: T,3}4
5let item: Item<string> = {6  prop: "value"7};

您可以將此視為將引數傳遞給函式,只有傳回值是您可以使用的類型。

類別(當用作類型時)、類型別名和介面都要求您傳遞類型引數。函式和函式類型沒有參數化泛型。

類別

1class Item<T> {2  prop: T;3  constructor(param: T) {4    this.prop = param;5  }6}7
8let item1: Item<number> = new Item(42); // Works!9let item2: Item = new Item(42); // Error!
9:12-9:15: Cannot use `Item` [1] without 1 type argument. [missing-type-arg]

類型別名

1type Item<T> = {2  prop: T,3};4
5let item1: Item<number> = { prop: 42 }; // Works!6let item2: Item = { prop: 42 }; // Error!
6:12-6:15: Cannot use `Item` [1] without 1 type argument. [missing-type-arg]

介面

1interface HasProp<T> {2  prop: T,3}4
5class Item {6  prop: string;7}8
9Item.prototype as HasProp<string>; // Works!10Item.prototype as HasProp; // Error!
10:19-10:25: Cannot use `HasProp` [1] without 1 type argument. [missing-type-arg]

為參數化泛型加入預設值

您也可以為參數化泛型提供預設值,就像函式的參數一樣。

1type Item<T: number = 1> = {2  prop: T,3};4
5let foo: Item<> = { prop: 1 };6let bar: Item<2> = { prop: 2 };

使用類型時,您必須始終包含括號 <>(就像函式呼叫的圓括號一樣)。

變異符號

您也可以透過變異符號指定泛型的子類型行為。預設情況下,泛型行為不變,但您可以在其宣告中加入 + 使其行為協變,或加入 - 使其行為逆變。請參閱 我們關於 Flow 中變異的說明文件,以取得更多有關變異的資訊。

變異符號讓您可以更具體地說明您打算如何使用泛型,讓 Flow 能夠執行更精確的類型檢查。例如,您可能希望此關係成立

1type GenericBox<+T> = T;2
3const x: GenericBox<number> = 3;4x as GenericBox<number| string>;

沒有 + 變異符號,就無法完成上述範例

1type GenericBoxError<T> = T;2
3const x: GenericBoxError<number> = 3;4x as GenericBoxError<number| string>; // number | string is not compatible with number.
4:1-4:1: Cannot cast `x` to `GenericBoxError` because string [1] is incompatible with number [2] in type argument `T` [3]. [incompatible-cast]

請注意,如果您使用變異符號註解您的泛型,則 Flow 會檢查以確保這些類型僅出現在對應變異符號有意義的位置。例如,您無法宣告泛型類型參數為協變行為,並在逆變位置使用它

1type NotActuallyCovariant<+T> = (T) => void;
1:34-1:34: Cannot use `T` [1] in an input position because `T` [1] is expected to occur only in output positions. [incompatible-variance]