跳至主要内容

联合

有时,创建一种其中之一的类型集很有用。例如,您可能希望编写一个接受一组原始值类型的函数。为此,Flow 支持联合类型

1function toStringPrimitives(value: number | boolean | string): string {2  return String(value);3}4
5toStringPrimitives(1);       // Works!6toStringPrimitives(true);    // Works!7toStringPrimitives('three'); // Works!8
9toStringPrimitives({prop: 'val'}); // Error!
10toStringPrimitives([1, 2, 3, 4, 5]); // Error!
9:20-9:32: Cannot call `toStringPrimitives` with object literal bound to `value` because: [incompatible-call] Either object literal [1] is incompatible with number [2]. Or object literal [1] is incompatible with boolean [3]. Or object literal [1] is incompatible with string [4].
10:20-10:34: Cannot call `toStringPrimitives` with array literal bound to `value` because: [incompatible-call] Either array literal [1] is incompatible with number [2]. Or array literal [1] is incompatible with boolean [3]. Or array literal [1] is incompatible with string [4].

联合类型语法

联合类型是通过竖线 | 连接的任意数量的类型。

Type1 | Type2 | ... | TypeN

您也可以添加一个前导竖线,这在将联合类型拆分为多行时很有用。

type Foo =
| Type1
| Type2
| ...
| TypeN

联合类型的每个成员都可以是任何类型,甚至可以是另一个联合类型。

1type Numbers = 1 | 2;2type Colors = 'red' | 'blue'3
4type Fish = Numbers | Colors;

如果您启用了Flow 枚举,它们可能是字面量类型联合的替代方案。

联合简写

某些类型 Tnullvoid 的联合很常见,因此我们提供了一个称为可能类型的简写,使用 ? 前缀。类型 ?T 等效于 T | null | void

1function maybeString(x: ?string) { /* ... */ }2maybeString('hi'); // Works!3maybeString(null); // Works!4maybeString(undefined); // Works!

所有现有类型的联合是mixed 类型

1function everything(x: mixed) { /* ... */ }2everything(1); // Works!3everything(true); // Works!4everything(null); // Works!5everything({foo: 1}); // Works!6everything(new Error()); // Works!

联合与细化

当您有一个值为联合类型时,通常将它拆开并分别处理每个单独的类型很有用。在 Flow 中使用联合类型,您可以细化该值以使其成为单个类型。

例如,如果我们有一个值为联合类型,它是一个 number、一个 boolean 或一个 string,我们可以使用 JavaScript 的 typeof 运算符分别处理数字情况。

1function toStringPrimitives(value: number | boolean | string) {2  if (typeof value === 'number') {3    return value.toLocaleString([], {maximumSignificantDigits: 3}); // Works!4  }5  // ...6}

通过检查我们值的 typeof 并测试它是否为 number,Flow 知道在该块内它只是一种数字。然后,我们可以在该块内编写将我们的值视为数字的代码。

联合类型需要一个输入,但需要所有输出

当调用接受联合类型的函数时,我们必须传入其中一种类型。但在函数内部,我们必须处理所有可能的类型

让我们使用细化重写该函数以分别处理每种类型。

1function toStringPrimitives(value: number | boolean | string): string {2  if (typeof value === 'number') {3    return String(value);4  } else if (typeof value === 'boolean') {5    return String(value);6  }7  return value; // If we got here, it's a `string`!8}

如果我们没有处理我们值的每种可能类型,Flow 将会给我们一个错误

1function toStringPrimitives(value: number | boolean | string): string {2  if (typeof value === 'number') {3    return String(value);4  }5  return value; // Error!
6}
5:10-5:14: Cannot return `value` because boolean [1] is incompatible with string [2]. [incompatible-return]

不相交对象联合

Flow 中有一种特殊的联合类型称为“不相交对象联合”,它可以与细化一起使用。这些不相交对象联合由任意数量的对象类型组成,每个对象类型都由单个属性标记。

例如,假设我们有一个函数用于处理在向服务器发送请求后从服务器接收的响应。当请求成功时,我们将收到一个带有 type 属性设置为 'success' 和我们已更新的 value 的对象。

{type: 'success', value: 23}

当请求失败时,我们将收到一个带有 type 设置为 'error' 和描述错误的 error 属性的对象。

{type: 'error', error: 'Bad request'}

我们可以尝试在一个对象类型中表达这两个对象。但是,我们很快就会遇到问题,即我们知道某个属性是否存在是基于 type 属性的,但 Flow 不知道。

1type Response = {2  type: 'success' | 'error',3  value?: number,4  error?: string5};6
7function handleResponse(response: Response) {8  if (response.type === 'success') {9    const value: number = response.value; // Error!
10 } else {11 const error: string = response.error; // Error!
12 }13}
9:27-9:40: Cannot assign `response.value` to `value` because undefined [1] is incompatible with number [2]. [incompatible-type]
11:27-11:40: Cannot assign `response.error` to `error` because undefined [1] is incompatible with string [2]. [incompatible-type]

尝试将这两个单独的类型合并为一个类型只会给我们带来麻烦。

相反,如果我们创建两个对象类型的联合类型,Flow 将能够根据 type 属性知道我们正在使用哪个对象。

1type Response =2  | {type: 'success', value: 23}3  | {type: 'error', error: string};4
5function handleResponse(response: Response) {6  if (response.type === 'success') {7    const value: number = response.value; // Works!8  } else {9    const error: string = response.error; // Works!10  }11}

为了使用这种模式,必须有一个键存在于联合中的每个对象中(在我们上面的示例中,type),并且每个对象必须为该键设置一个不同的字面量类型(在我们上面的示例中,字符串 'success' 和字符串 'error')。您可以使用任何类型的字面量类型,包括数字和布尔值。

带有精确类型的不相交对象联合

不相交联合要求您使用单个属性来区分每个对象类型。您不能通过不同的属性来区分两个不同的不精确对象

1type Success = {success: true, value: boolean, ...};2type Failed  = {error: true, message: string, ...};3
4function handleResponse(response:  Success | Failed) {5  if (response.success) {6    const value: boolean = response.value; // Error!
7 }8}
6:37-6:41: Cannot get `response.value` because property `value` is missing in object type [1]. [prop-missing]

这是因为在 Flow 中,传递一个具有比不精确对象类型预期更多属性的对象值是可以的(由于宽度子类型化)。

1type Success = {success: true, value: boolean, ...};2type Failed  = {error: true, message: string, ...};3
4function handleResponse(response:  Success | Failed) {5  // ...6}7
8handleResponse({9  success: true,10  error: true,11  value: true,12  message: 'hi'13});

除非对象以某种方式相互冲突,否则无法区分它们。

但是,为了解决这个问题,您可以使用精确对象类型

1type Success = {success: true, value: boolean};2type Failed  = {error: true, message: string};3
4type Response = Success | Failed;5
6function handleResponse(response: Response) {7  if (response.success) {8    const value: boolean = response.value;9  } else {10    const message: string = response.message;11  }12}

使用精确对象类型,我们不能有额外的属性,因此对象相互冲突,我们能够区分它们。