Skip to main content

交集

有时创建一个包含所有其他类型的类型很有用。例如,你可能想要编写一个接受实现两个不同 接口 的值的函数:

¥Sometimes it is useful to create a type which is all of a set of other types. For example, you might want to write a function which accepts a value that implements two different interfaces:

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

交集类型语法

¥Intersection type syntax

交集类型是由 & 符号 & 连接的任意数量的类型。

¥Intersection types are any number of types which are joined by an ampersand &.

Type1 & Type2 & ... & TypeN

你还可以添加一个前导 & 符号,这在将交叉类型分解为多条线时非常有用。

¥You may also add a leading ampersand which is useful when breaking intersection types onto multiple lines.

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

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

¥Each of the members of a intersection type can be any type, even another intersection type.

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

type Baz = Foo & Bar;

交集类型要求全进单出

¥Intersection types require all in, but one out

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

¥Intersection types are the opposite of union types. When calling a function that accepts an intersection type, we must pass in all of those types. But inside of our function we only have to treat it as any one of those types.

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}

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

¥Even as we treat our value as just one of the types, we do not get an error because it satisfies all of them.

函数类型的交集

¥Intersection of function types

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

¥A common use of intersection types is to express functions that return different results based on the input we pass in. Suppose for example that we want to write the type of a function that:

  • 返回一个字符串,当我们传入值 "string" 时,

    ¥returns a string, when we pass in the value "string",

  • 当我们传入值 "number" 时,返回一个数字,并且

    ¥returns a number, when we pass in the value "number", and

  • 当我们传入任何其他字符串时,返回任何可能的类型 (mixed)。

    ¥returns any possible type (mixed), when we pass in any other string.

该函数的类型将是

¥The type of this function will be

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

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

¥Each line in the above definition is called an overload, and we say that functions of type Fn are overloaded.

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

¥Note the use of parentheses around the arrow types. These are necessary to override the precedence of the "arrow" constructor over the intersection.

调用重载函数

¥Calling an overloaded function

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

¥Using the above definition we can declare a function fn that has the following behavior:

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!

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

¥Flow achieves this behavior by matching the type of the argument to the first overload with a compatible parameter type. Notice for example that the argument "string" matches both the first and the last overload. Flow will just pick the first one. If no overload matches, Flow will raise an error at the call site.

声明重载函数

¥Declaring overloaded functions

声明相同函数 fn 的等效方法是使用连续的 "声明函数" 语句

¥An equivalent way to declare the same function fn would be by using consecutive "declare function" statements

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

Flow 的一个限制是它无法根据交集类型检查函数体。换句话说,如果我们在上述声明之后立即为 fn 提供以下实现

¥A limitation in Flow is that it can't check the body of a function against an intersection type. In other words, if we provided the following implementation for fn right after the above declarations

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

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

¥Flow silently accepts it (and uses Fn as the inferred type), but does not check the implementation against this signature. This makes this kind of declaration a better suited candidate for library definitions, where implementations are omitted.

对象类型的交集

¥Intersections of object types

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

¥When you create an intersection of inexact object types, you are saying that your object satisfies each member of the intersection.

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

¥For example, when you create an intersection of two inexact objects with different sets of properties, it will result in an object with all of the properties.

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

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

¥When you have properties that overlap by having the same name, Flow follows the same strategy as with overloaded functions: it will return the type of the first property that matches this name.

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

¥For example, if you merge two inexact objects with a property named prop, first with a type of number and second with a type of boolean, accessing prop will return 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!

要组合精确的对象类型,你应该使用 对象类型传播 代替:

¥To combine exact object types, you should use object type spread instead:

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 中实现交叉类型的特定于顺序的方式通常可能看起来违反直觉。在集合中,交集的操作数可以任意改变顺序(交换律)。因此,更好的做法是使用对象类型扩展来定义对对象类型的这种操作,其中可以更好地指定排序语义。

¥Note: When it comes to objects, the order-specific way in which intersection types are implemented in Flow, may often seem counter-intuitive from a set theoretic point of view. In sets, the operands of intersection can change order arbitrarily (commutative property). For this reason, it is a better practice to define this kind of operation over object types using object type spread where the ordering semantics are better specified.

不可能的交叉点类型

¥Impossible intersection types

使用交集类型,可以创建在运行时无法创建的类型。交叉类型将允许你组合任何类型,甚至是相互冲突的类型。

¥Using intersection types, it is possible to create types which are impossible to create at runtime. Intersection types will allow you to combine any set of types, even ones that conflict with one another.

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

¥For example, you can create an intersection of a number and a string.

1type NumberAndString = number & string;2
3function func(value: NumberAndString) { /* ... */ }4
5func(3.14); // Error!6func('hi'); // Error!

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

¥But you can't possibly create a value which is both a number and a string, but you can create a type for it. There's no practical use for creating types like this, but it's a side effect of how intersection types work.

创建不可能类型的一种意外方法是创建 确切的对象类型 的交集。例如:

¥An accidental way to create an impossible type is to create an intersection of exact object types. For example:

1function func(obj: {a: number} & {b: string}) { /* ... */ }2
3func({a: 1}); // Error!4func({b: 'hi'}); // Error!5func({a: 1, b: 'hi'}); // Error!

一个对象不可能既具有属性 a 而没有其他属性,又同时具有属性 b 而没有其他属性。

¥It's not possible for an object to have exactly the property a and no other properties, and simultaneously exactly the property b and no other properties.