跳至主要内容

泛型

泛型(有时称为多态类型)是一种抽象类型的方式。

想象一下编写以下 `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]