泛型
泛型(有時稱為多型類型)是一種抽象類型的方法。
想像一下撰寫下列 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]