泛型
泛型(有时称为多态类型)是一种抽象类型的方式。
想象一下编写以下 `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]