跳到主要内容

交集

有时,创建一种类型很有用,这种类型是所有其他类型集合。例如,您可能想要编写一个函数,该函数接受实现两个不同接口的值

1interface Serializable {2  serialize(): string;3}4
5interface HasLength {6  length: number;7}8
9function func(value: Serializable & HasLength) {10  // ...11}12
13func({14  length: 3,15  serialize() {16    return '3';17  },18}); // Works19
20func({length: 3}); // Error! Doesn't implement both interfaces
20:6-20:16: Cannot call `func` with object literal bound to `value` because property `serialize` is missing in object literal [1] but exists in `Serializable` [2]. [prop-missing]

交集类型语法

交集类型是任何数量的类型,它们通过一个与符号 & 连接。

Type1 & Type2 & ... & TypeN

您也可以添加一个前导与符号,这在将交集类型分解到多行时很有用。

type Foo =
& Type1
& Type2
& ...
& TypeN

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

type Foo = Type1 & Type2;
type Bar = Type3 & Type4;

type Baz = Foo & Bar;

交集类型要求所有输入,但只输出一个

交集类型与联合类型相反。当调用接受交集类型的函数时,我们必须传入所有这些类型。但在函数内部,我们只需要将其视为这些类型中的任何一个

1type A = {a: number, ...};2type B = {b: boolean, ...};3type C = {c: string, ...};4
5function func(value: A & B & C) {6  const a: A = value;7  const b: B = value;8  const c: C = value;9}

即使我们将值视为其中一种类型,我们也不会收到错误,因为它满足所有类型。

函数类型的交集

交集类型的一个常见用途是表达根据传入的输入返回不同结果的函数。例如,假设我们想要编写一个函数的类型,该函数

  • 当我们传入值 "string" 时,返回一个字符串,
  • 当我们传入值 "number" 时,返回一个数字,并且
  • 当我们传入任何其他字符串时,返回任何可能的类型 (mixed)。

此函数的类型将是

1type Fn =2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);

上述定义中的每一行都称为重载,我们说类型为 Fn 的函数是重载的

请注意箭头类型周围使用了括号。这些是必要的,以覆盖“箭头”构造函数优先于交集的优先级。

调用重载函数

使用上述定义,我们可以声明一个函数 fn,该函数具有以下行为

1declare const fn:2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);5
6const s: string = fn("string"); // Works7const n: number = fn("number"); // Works8const b: boolean = fn("boolean"); // Error!
8:20-8:32: Cannot assign `fn(...)` to `b` because mixed [1] is incompatible with boolean [2]. [incompatible-type]

Flow 通过将参数的类型与具有兼容参数类型的第一个重载匹配来实现此行为。例如,请注意参数 "string" 同时匹配第一个和最后一个重载。Flow 只会选择第一个。如果没有任何重载匹配,Flow 将在调用点引发错误。

声明重载函数

声明相同函数 fn 的另一种等效方法是使用连续的“declare function”语句

1declare function fn(x: "string"): string;2declare function fn(x: "number"): number;3declare function fn(x: string): mixed;

Flow 的一个限制是它不能检查函数体是否与交集类型匹配。换句话说,如果我们在上述声明之后提供了以下 fn 的实现

1function fn(x: mixed) {2  if (x === "string") { return ""; }3  else if (x === "number") { return 0; }4  else { return null; }5}

Flow 会静默接受它(并使用 Fn 作为推断类型),但不会检查实现是否与该签名匹配。这使得这种声明更适合库定义,其中省略了实现。

对象类型的交集

当您创建不精确对象类型的交集时,您是在说您的对象满足交集中的每个成员。

例如,当您创建具有不同属性集的两个不精确对象的交集时,它将生成一个具有所有属性的对象。

1type One = {foo: number, ...};2type Two = {bar: boolean, ...};3
4type Both = One & Two;5
6const value: Both = {7  foo: 1,8  bar: true9};

当您具有具有相同名称的重叠属性时,Flow 会遵循与重载函数相同的策略:它将返回与该名称匹配的第一个属性的类型。

例如,如果您合并两个具有名为 prop 的属性的不精确对象,第一个对象的类型为 number,第二个对象的类型为 boolean,则访问 prop 将返回 number

1type One = {prop: number, ...};2type Two = {prop: boolean, ...};3
4declare const both: One & Two;5
6const prop1: number = both.prop; // Works7const prop2: boolean = both.prop; // Error!
7:24-7:32: Cannot assign `both.prop` to `prop2` because number [1] is incompatible with boolean [2]. [incompatible-type]

要组合精确对象类型,您应该使用对象类型扩展

1type One = {foo: number};2type Two = {bar: boolean};3
4type Both = {5  ...One,6  ...Two,7};8
9const value: Both = {10  foo: 1,11  bar: true12};

注意:在对象方面,Flow 中实现交集类型的特定顺序方式,从集合论的角度来看可能经常显得违反直觉。在集合中,交集的操作数可以任意改变顺序(交换律)。出于这个原因,使用对象类型扩展来定义这种操作是一种更好的做法,在对象类型扩展中,排序语义得到了更好的指定。

不可能的交集类型

使用交集类型,可以创建在运行时不可能创建的类型。交集类型允许您组合任何类型的集合,即使是相互冲突的类型。

例如,您可以创建数字和字符串的交集。

1type NumberAndString = number & string;2
3function func(value: NumberAndString) { /* ... */ }4
5func(3.14); // Error!
6func('hi'); // Error!
5:6-5:9: Cannot call `func` with `3.14` bound to `value` because number [1] is incompatible with string [2]. [incompatible-call]
6:6-6:9: Cannot call `func` with `'hi'` bound to `value` because string [1] is incompatible with number [2]. [incompatible-call]

但是,您不可能创建一个既是数字又是字符串的值,但您可以为其创建类型。创建这种类型没有实际用途,但这只是交集类型工作方式的副作用。

创建不可能类型的意外方式是创建精确对象类型的交集。例如

1function func(obj: {a: number} & {b: string}) { /* ... */ }2
3func({a: 1}); // Error!
4func({b: 'hi'}); // Error!
5func({a: 1, b: 'hi'}); // Error!
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]

一个对象不可能同时具有恰好 a 属性而没有其他属性,以及恰好 b 属性而没有其他属性。