Skip to main content

注释要求

注意:从版本 0.199 开始,Flow 使用 局部类型推断 作为其推断算法。本节中的规则反映了该推断方案的主要设计特性。

¥Note: As of version 0.199 Flow uses Local Type Inference as its inference algorithm. The rules in this section reflect the main design features in this inference scheme.

Flow 尝试避免对程序部分要求类型注释,因为这些部分可以轻松地从表达式、变量、参数等的直接上下文推断出类型。

¥Flow tries to avoid requiring type annotation for parts of programs where types can easily be inferred from the immediate context of an expression, variable, parameter, etc.

变量声明

¥Variable declarations

以下面的变量定义为例

¥Take for example the following variable definition

const len = "abc".length;

推断 len 类型所需的所有信息都包含在初始化程序 "abc".length 中。Flow 将首先确定 "abc" 是字符串,然后确定字符串的 length 属性是数字。

¥All information necessary to infer the type of len is included in the initializer "abc".length. Flow will first determine that "abc" is a string, and then that the length property of a string is a number.

相同的逻辑可以应用于所有类似 const 的初始化。当变量初始化跨越多个语句时,事情会变得更加复杂,例如

¥The same logic can be applied for all const-like initializations. Where things get a little more complicated is when variable initialization spans across multiple statements, for example in

1declare const maybeString: ?string;2
3let len;4if (typeof maybeString === "string") {5  len = maybeString.length;6} else {7  len = 0;8}

Flow 仍然可以确定 lennumber,但为了做到这一点,它会向前查找多个初始化语句。有关各种初始值设定项模式如何确定变量类型以及何时需要变量声明上的注释的详细信息,请参阅 变量声明 部分。

¥Flow can still determine that len is a number, but in order to do so it looks ahead to multiple initializer statements. See section on variable declarations for details on how various initializer patterns determine the type of a variable, and when an annotation on a variable declaration is necessary.

函数参数

¥Function Parameters

与变量声明不同,这种 "展望" 推断不能用于确定函数参数的类型。考虑功能

¥Unlike variable declarations, this kind of "lookahead" reasoning cannot be used to determine the type of function parameters. Consider the function

function getLength(x) {
return x.length;
}

我们可以在多种类型的 x 上访问并返回 length 属性:具有 length 属性的对象或字符串,仅举几例。如果稍后在程序中我们对 getLength 有以下调用

¥There are many kinds of x on which we could access and return a length property: an object with a length property, or a string, just to name a few. If later on in the program we had the following calls to getLength

getLength("abc");
getLength({length: 1});

一个可能的推论是 xstring | { length: number }。然而,这意味着 getLength 的类型由当前程序的任何部分确定。这种全局推断可能会导致令人惊讶的远距离行动行为,因此是可以避免的。相反,Flow 要求对函数参数进行注释。未能提供此类类型注释会在参数 x 上表现为 [missing-local-annot] 错误,并使用 x: any 检查函数体:

¥one possible inference would be that x is a string | { length: number }. What this implies, however, is that the type of getLength is determined by any part of the current program. This kind of global reasoning can lead to surprising action-at-a-distance behavior, and so is avoided. Instead, Flow requires that function parameters are annotated. Failure to provide such a type annotation manifests as a [missing-local-annot] error on the parameter x, and the body of the function is checked with x: any:

1function getLength(x) {2  return x.length;3}4
5const n = getLength(1); // no error since getLength's parameter type is 'any'

要修复此错误,只需将 x 注释为

¥To fix this error, one can simply annotate x as

1function getLength(x: string) {2  return x.length;3}

对于类方法也有同样的要求

¥The same requirement holds for class methods

1class WrappedString {2  data: string;3  setStringNoAnnotation(x) {4    this.data = x;5  }6  setString(x: string) {7    this.data = x;8  }9}

上下文类型

¥Contextual Typing

函数参数并不总是需要显式注释。对于函数调用的回调函数,可以轻松地从直接上下文中推断出参数类型。例如考虑以下代码

¥Function parameters do not always need to be explicitly annotated. In the case of a callback function to a function call, the parameter type can easily be inferred from the immediate context. Consider for example the following code

const arr = [0, 1, 2];
const arrPlusOne = arr.find(x => x % 2 === 1);

Flow 推断出 arr 的类型是 Array<number>。结合 Array.find 的内置信息,Flow 可以确定 x => x % 2 + 1 的类型需要是 number => mixed。该类型充当 Flow 的提示,并提供足够的信息来确定 x 的类型为 number

¥Flow infers that the type of arr is Array<number>. Combining this with the builtin information for Array.find, Flow can determine that the type of x => x % 2 + 1 needs to be number => mixed. This type acts as a hint for Flow and provides enough information to determine the type of x as number.

例如,任何附带注释都可能充当函数参数的提示

¥Any attendant annotation can potentially act as a hint to a function parameter, for example

1const fn1: (x: number) => number = x => x + 1;

但是,也有可能注解不能用作函数参数提示:

¥However, it is also possible that an annotation cannot be used as a function parameter hint:

1const fn2: mixed = x => x + 1;

在此示例中,mixed 类型根本不包含足够的信息来提取 x 的候选类型。

¥In this example the mixed type simply does not include enough information to extract a candidate type for x.

Flow 可以推断未注释参数的类型,即使它们嵌套在对象等其他表达式中也是如此。例如在

¥Flow can infer the types for unannotated parameters even when they are nested within other expressions like objects. For example in in

1const fn3: {f: (number) => void} = {f: (x) => {x as string}};

Flow 会将 number 推断为 x 的类型,因此转换失败。

¥Flow will infer number as the type of x, and so the cast fails.

函数返回类型

¥Function Return Types

与函数参数不同,函数的返回类型一般不需要注释。因此上述 getLength 的定义不会引发任何 Flow 错误。

¥Unlike function parameters, a function's return type does not need to be annotated in general. So the above definition of getLength won't raise any Flow errors.

然而,这条规则有几个值得注意的例外。第一个是类方法。如果我们在 WrappedString 类中包含一个返回内部 data 属性的 getString 方法:

¥There are, however, a couple of notable exceptions to this rule. The first one is class methods. If we included to the WrappedString class a getString method that returns the internal data property:

1class WrappedString {2  data: string;3  getString(x: string) {4    return this.data;5  }6}

Flow 会诉说 getString 在返回值上缺少注释。

¥Flow would complain that getString is missing an annotation on the return.

第二个例外是递归定义。一个简单的例子是

¥The second exception is recursive definitions. A trivial example of this would be

1function foo() {2  return bar();3}4
5function bar() {6  return foo();7}

上面的代码引发了 [definition-cycle] 错误,该错误指向形成依赖循环的两个位置,即两个缺少的返回注释。向任一函数添加返回注释都可以解决该问题。

¥The above code raises a [definition-cycle] error, which points to the two locations that form a dependency cycle, the two missing return annotations. Adding a return annotation to either function would resolve the issue.

实际上,对方法返回的注释的要求是递归定义限制的特殊情况。通过访问 this 可以实现递归。

¥Effectively, the requirement on an annotation for method returns is a special-case of the recursive definition restriction. The recursion is possible through access on this.

通用调用

¥Generic Calls

在对 通用函数 的调用中,结果的类型可能取决于作为参数传入的值的类型。本节讨论在未显式提供类型参数时如何计算此结果。

¥In calls to generic functions the type of the result may depend on the types of the values passed in as arguments. This section discusses how this result is computed, when type arguments are not explicitly provided.

例如考虑定义

¥Consider for example the definition

declare function map<T, U>(
f: (T) => U,
array: $ReadOnlyArray<T>,
): Array<U>;

以及带有参数 x => x + 1[1, 2, 3] 的潜在调用:

¥and a potential call with arguments x => x + 1 and [1, 2, 3]:

map(x => x + 1, [1, 2, 3]);

这里 Flow 推断出 x 的类型是 number

¥Here Flow infers that the type of x is number.

泛型调用的其他一些常见示例是调用泛型 Set 的构造函数或从 React 库调用 useState

¥Some other common examples of generic calls are calling the constructor of the generic Set class or calling useState from the React library:

1const set = new Set([1, 2, 3]);2
3import {useState} from 'react';4const [num, setNum] = useState(42);

这里的 Flow 推断出 set 的类型是 Set<number>numsetNum 分别是 number(number) => void

¥Flow here infers that the type of set is Set<number>, and that num and setNum are number and (number) => void, respectively.

计算解决方案

¥Computing a Solution

计算通用调用的结果相当于:

¥Computing the result of a generic call amounts to:

  1. TU 提出一个不包含通用零件的解决方案,

    ¥coming up with a solution for T and U that does not contain generic parts,

  2. TU 替换为 map 签名中的解决方案,并且

    ¥replacing T and U with the solution in the signature of map, and

  3. map 的新签名执行调用。

    ¥performing a call to this new signature of map.

此过程的设计考虑了两个目标:

¥This process is designed with two goals in mind:

  • 健全性。当我们到达步骤 (3) 时,结果需要导致正确的调用。

    ¥Soundness. The results need to lead to a correct call when we reach step (3).

  • 完整性。Flow 生成的类型需要尽可能精确且信息丰富,以确保程序的其他部分能够成功检查。

    ¥Completeness. The types Flow produces need to be as precise and informative as possible, to ensure that other parts of the program will be successfully checked.

让我们看看这两个目标如何在上面的 map 示例中发挥作用。

¥Let's see how these two goals come into play in the map example from above.

Flow 检测到 $ReadOnlyArray<T> 需要与 [1, 2, 3] 的类型兼容。因此可以推断 Tnumber

¥Flow detects that $ReadOnlyArray<T> needs to be compatible with the type of [1, 2, 3]. It can therefore infer that T is number.

有了 T 的知识,现在可以成功检查 x => x + 1。参数 x 根据上下文键入为 number,因此结果 x + 1 也是一个数字。这个最终约束也允许我们将 U 计算为 number

¥With the knowledge of T it can now successfully check x => x + 1. The parameter x is contextually typed as number, and thus the result x + 1 is also a number. This final constraint allows us to compute U as a number too.

用上述方案替换通用部分后,map 的新签名为

¥The new signature of map after replacing the generic parts with the above solution is

(f: (number) => number, array: $ReadOnlyArray<number>) => Array<number>

很容易看出调用已成功检查。

¥It is easy to see that the call would be successfully checked.

多态调用期间的错误

¥Errors during Polymorphic Calls

如果上述过程顺利进行,你应该不会看到与调用相关的任何错误。当这个过程失败时会发生什么?

¥If the above process goes on smoothly, you should not be seeing any errors associated with the call. What happens though when this process fails?

此过程可能失败的原因有两个:

¥There are two reasons why this process could fail:

欠约束类型参数

¥Under-constrained Type Parameters

在某些情况下,Flow 可能没有足够的信息来决定类型参数的类型。让我们再次检查对内置通用 Set 构造函数的调用,这次不传递任何参数:

¥There are cases where Flow might not have enough information to decide the type of a type parameter. Let's examine again a call to the builtin generic Set class constructor, this time without passing any arguments:

1const set = new Set();2set.add("abc");

在调用 new Set 期间,我们没有为 Flow 提供足够的信息来确定 T 的类型,即使随后对 set.add 的调用清楚地表明 T 将是一个字符串。请记住,类型参数的推断对于调用来说是本地的,因此 Flow 不会尝试在后面的语句中向前查看来确定这一点。

¥During the call to new Set, we are not providing enough information for Flow to determine the type for T, even though the subsequent call to set.add clearly implies that T will be a string. Remember that inference of type arguments is local to the call, so Flow will not attempt to look ahead in later statements to determine this.

在没有信息的情况下,Flow 可以随意将任何类型推断为 Tanymixedempty 等。这种决定是不可取的,因为它可能会导致令人惊讶的结果。例如,如果我们默默地决定使用 Set<empty>,那么对 set.add("abc") 的调用将会失败,因为 stringempty 之间不兼容,并且没有明确指示 empty 来自哪里。

¥In the absence of information, Flow would be at liberty to infer any type as T: any, mixed, empty, etc. This kind of decision is undesirable, as it can lead to surprising results. For example, if we silently decided on Set<empty> then the call to set.add("abc") would fail with an incompatibility between string and empty, without a clear indication of where the empty came from.

因此,在这种情况下,你会收到 [underconstrained-implicit-instantiation] 错误。修复此错误的方法是添加类型注释。有几种可能的方法可以做到这一点:

¥So instead, in situations like this, you'll get an [underconstrained-implicit-instantiation] error. The way to fix this error is by adding a type annotation. There a few potential ways to do this:

  • 通过以下两种方式之一在调用站点添加注释:

    ¥Add an annotation at the call-site in one of two ways:

    • 显式类型参数

      ¥an explicit type argument

      const set = new Set<string>();
    • 初始化变量的注释:

      ¥an annotation on the initialization variable:

      const set: Set<string> = new Set();
  • 在类定义的类型参数 T 上添加默认类型:

    ¥Add a default type on the type parameter T at the definition of the class:

    declare class SetWithDefault<T = string> extends $ReadOnlySet<T> {
    constructor(iterable?: ?Iterable<T>): void;
    // more methods ...
    }

    如果调用站点没有任何类型信息,Flow 将使用默认类型 T 作为推断的类型参数:

    ¥In the absence of any type information at the call-site, Flow will use the default type of T as the inferred type argument:

    const defaultSet = new SetWithDefault(); // defaultSet is SetWithDefault<string>

不兼容错误

¥Incompatibility Errors

即使 Flow 设法在泛型调用中推断类型参数的非泛型类型,这些类型仍可能导致当前调用或稍后的代码中不兼容。

¥Even when Flow manages to infer non-generic types for the type parameters in a generic call, these types might still lead to incompatibilities either in the current call or in code later on.

例如,如果我们对 map 有以下调用:

¥For example, if we had the following call to map:

1declare function map<T, U>(f: (T) => U, array: $ReadOnlyArray<T>): Array<U>;2map(x => x + 1, [{}]);

Flow 会将 T 推断为 {},因此将 x 键入为 {}。这会在检查箭头函数时导致错误,因为对象上不允许进行 + 操作。

¥Flow will infer T as {}, and therefore type x as {}. This will cause an error when checking the arrow function since the + operation is not allowed on objects.

最后,错误的常见来源是泛型调用中的推断类型对于调用本身来说是正确的,但并不表示代码稍后的预期用途。例如,考虑

¥Finally, a common source of errors is the case where the inferred type in a generic call is correct for the call itself, but not indicative of the expected use later in the code. For example, consider

1import {useState} from 'react';2const [str, setStr] = useState("");3
4declare const maybeString: ?string;5setStr(maybeString);

将字符串 "" 传递给 useState 的调用会使 Flow 推断 string 作为状态类型。因此,稍后调用时,setStr 也会期望 string 作为输入,因此传递 ?string 将是一个错误。

¥Passing the string "" to the call to useState makes Flow infer string as the type of the state. So setStr will also expect a string as input when called later on, and therefore passing a ?string will be an error.

同样,要修复此错误,只需在调用 useState 时注释预期的 "更宽的" 状态类型即可:

¥Again, to fix this error it suffices to annotate the expected "wider" type of state when calling useState:

const [str, setStr] = useState<?string>("");

空数组字面量

¥Empty Array Literals

空数组字面量 ([]) 在 Flow 中进行特殊处理。你可以阅读有关他们的 行为和要求 的信息。

¥Empty array literals ([]) are handled specially in Flow. You can read about their behavior and requirements.