Skip to main content

文件签名(类型优先)

Flow 通过按依赖顺序单独处理每个文件来检查代码库。对于包含用于检查过程的重要键入信息的每个文件,需要提取签名并将其存储在主存储器中,以用于依赖于它的文件。Flow 依赖于文件边界上可用的注释来构建这些签名。我们将 Flow 架构的这一要求称为“类型优先”。

¥Flow checks codebases by processing each file separately in dependency order. For every file containing important typing information for the checking process, a signature needs to be extracted and stored in main memory, to be used for files that depend on it. Flow relies on annotations available at the boundaries of files to build these signatures. We call this requirement of Flow's architecture Types-First.

这种架构的好处是双重的:

¥The benefit of this architecture is dual:

  1. 它极大地提高了性能,特别是在重新检查时。假设我们希望 Flow 检查文件 foo.js,但它尚未检查其依赖。Flow 只需查看导出周围的注释即可提取依赖签名。此过程主要是语法性的,因此比 Flow 的旧版本(v0.125 之前)用于生成签名的完整类型推断要快得多。

    ¥It dramatically improves performance, in particular when it comes to rechecks. Suppose we want Flow to check a file foo.js, for which it hasn't checked its dependencies yet. Flow extracts the dependency signatures just by looking at the annotations around the exports. This process is mostly syntactic, and therefore much faster than full type inference that legacy versions of Flow (prior to v0.125) used to perform in order to generate signatures.

  2. 它提高了错误可靠性。推断类型通常会变得复杂,并可能导致下游文件中报告错误,远离其实际来源。在文件的文件边界处键入注释可以帮助定位此类错误,并在引入这些错误的文件中解决它们。

    ¥It improves error reliability. Inferred types often become complicated, and may lead to errors being reported in downstream files, far away from their actual source. Type annotations at file boundaries of files can help localize such errors, and address them in the file that introduced them.

这种性能优势的代价是代码的导出部分需要用类型进行注释,或者是可以轻松推断类型的表达式(例如数字和字符串)。

¥The trade-off for this performance benefit is that exported parts of the code need to be annotated with types, or to be expressions whose type can be trivially inferred (for example numbers and strings).

有关类型优先架构的更多信息可以在 这个帖子 中找到。

¥More information on the Types-First architecture can be found in this post.

如何将代码库升级到 Types-First

¥How to upgrade your codebase to Types-First

注意:自 v0.134 以来,类型优先一直是默认模式,也是自 v0.143 以来唯一可用的模式。从那时起,不需要 .flowconfig 选项来启用它。如果你要从更早的版本升级代码库,这里有一些有用的工具。

¥Note: Types-first has been the default mode since v0.134 and the only available mode since v0.143. No .flowconfig options are necessary to enable it since then. In case you're upgrading your codebase from a much earlier version here are some useful tools.

升级 Flow 版本

¥Upgrade Flow version

类型优先模式在 0.125 版本中正式发布,但从 0.102 版本开始处于实验状态。如果你当前使用的是较旧的 Flow 版本,则必须首先升级 Flow。使用最新的 Flow 版本是从上述性能优势中获益的最佳方式。

¥Types-first mode was officially released with version 0.125, but has been available in experimental status as of version 0.102. If you are currently on an older Flow version, you’d have to first upgrade Flow. Using the latest Flow version is the best way to benefit from the performance benefits outlined above.

为 Types-First 准备代码库

¥Prepare your codebase for Types-First

类型优先需要在模块边界进行注释,以便为文件构建类型签名。如果缺少这些注释,则会引发 signature-verification-failure,并且代码相应部分的导出类型将为 any

¥Types-first requires annotations at module boundaries in order to build type signature for files. If these annotations are missing, then a signature-verification-failure is raised, and the exported type for the respective part of the code will be any.

要查看缺少哪些类型来使代码库类型优先做好准备,请将以下行添加到 .flowconfig 文件的 [options] 部分:

¥To see what types are missing to make your codebase types-first ready, add the following line to the [options] section of the .flowconfig file:

well_formed_exports=true

例如,考虑一个文件 foo.js,它将函数调用导出到 foo

¥Consider for example a file foo.js that exports a function call to foo

declare function foo<T>(x: T): T;
module.exports = foo(1);

函数调用的返回类型目前无法简单推断(由于多态性、重载等功能)。他们的结果需要注释,因此你会看到以下错误:

¥The return type of function calls is currently not trivially inferable (due to features like polymorphism, overloading etc.). Their result needs to be annotated and so you’d see the following error:

Cannot build a typed interface for this module. You should annotate the exports
of this module with types. Cannot determine the type of this call expression. Please
provide an annotation, e.g., by adding a type cast around this expression.
(`signature-verification-failure`)

4│ module.exports = foo(1);
^^^^^^

要解决此问题,你可以添加如下注释:

¥To resolve this, you can add an annotation like the following:

declare function foo<T>(x: T): T;
module.exports = foo(1) as number;

注意:从版本 0.134 开始,类型优先是默认模式。此模式会自动启用 well_formed_exports,因此你无需显式设置此标志即可看到这些错误。建议在这部分升级期间设置 types_first=false

¥Note: As of version 0.134, types-first is the default mode. This mode automatically enables well_formed_exports, so you would see these errors without explicitly setting this flag. It is advisable to set types_first=false during this part of the upgrade.

密封你的中间结果

¥Seal your intermediate results

当你在向代码库添加类型方面取得进展时,你可以包含目录,以便它们在提交新代码时不会回归,直到整个项目具有格式正确的导出。你可以通过在 .flowconfig 中添加如下行来完成此操作:

¥As you make progress adding types to your codebase, you can include directories so that they don’t regress as new code gets committed, and until the entire project has well-formed exports. You can do this by adding lines like the following to your .flowconfig:

well_formed_exports.includes=<PROJECT_ROOT>/path/to/directory

警告:这是子字符串检查,而不是正则表达式(出于性能原因)。

¥Warning: That this is a substring check, not a regular expression (for performance reasons).

适用于大型代码库的 codemod

¥A codemod for large codebases

向大型代码库添加必要的注释可能非常乏味。为了减轻这个负担,我们提供了一个基于 Flow 推断的 codemod,可用于批量注释多个文件。更多信息请参见 本教程

¥Adding the necessary annotations to large codebases can be quite tedious. To ease this burden, we are providing a codemod based on Flow's inference, that can be used to annotate multiple files in bulk. See this tutorial for more.

启用类型优先标志

¥Enable the types-first flag

消除签名验证错误后,你可以通过将以下行添加到 .flowconfig 文件的 [options] 部分来打开类型优先模式:

¥Once you have eliminated signature verification errors, you can turn on the types-first mode, by adding the following line to the [options] section of the .flowconfig file:

types_first=true

你还可以将 --types-first 传递给 flow checkflow start 命令。

¥You can also pass --types-first to the flow check or flow start commands.

types_first 隐含了之前的 well_formed_exports 标志。一旦此过程完成并启用类型优先,你就可以删除 well_formed_exports

¥The well_formed_exports flag from before is implied by types_first. Once this process is completed and types-first has been enabled, you can remove well_formed_exports.

不幸的是,无法为你的部分存储库启用类型优先模式;该开关影响当前 .flowconfig 管理的所有文件。

¥Unfortunately, it is not possible to enable types-first mode for part of your repo; this switch affects all files managed by the current .flowconfig.

注意:上述标志在带有 experimental. 前缀的 Flow >=0.102 版本中可用(在 v0.128 之前,它使用 whitelist 代替 includes):

¥Note: The above flags are available in versions of Flow >=0.102 with the experimental. prefix (and prior to v0.128, it used whitelist in place of includes):

experimental.well_formed_exports=true
experimental.well_formed_exports.whitelist=<PROJECT_ROOT>/path/to/directory
experimental.types_first=true

注意:如果你使用的版本默认启用类型优先(即 >=0.134),请确保在运行 codemods 时在 .flowconfig 中设置 types_first=false

¥Note: If you are using a version where types-first is enabled by default (ie. >=0.134), make sure you set types_first=false in your .flowconfig while running the codemods.

处理新引入的错误

¥Deal with newly introduced errors

除了我们之前提到的签名验证失败之外,在经典模式和类型优先模式之间切换可能会导致一些新的 Flow 错误。这些错误是由于基于注释的类型的解释方式与其各自的推断类型不同而造成的。

¥Switching between classic and types-first mode may cause some new Flow errors, besides signature-verification failures that we mentioned earlier. These errors are due differences in the way types based on annotations are interpreted, compared to their respective inferred types.

以下是一些常见的错误模式以及如何克服它们。

¥Below are some common error patterns and how to overcome them.

数组元组在导出中被视为常规数组

¥Array tuples treated as regular arrays in exports

在类型优先中,导出位置中的数组字面量

¥In types-first, an array literal in an export position

module.exports = [e1, e2];

被视为具有类型 Array<t1 | t2>,其中 e1e2 具有类型 t1t2,而不是元组类型 [t1, t2]

¥is treated as having type Array<t1 | t2>, where e1 and e2 have types t1 and t2, instead of the tuple type [t1, t2].

在经典模式下,推断类型同时包含这两种类型。这可能会导致导入文件时出现错误,例如期望在导入的第一个位置找到类型 t1

¥In classic mode, the inferred type encompassed both types at the same time. This might cause errors in importing files that expect for example to find type t1 in the first position of the import.

使固定:如果需要元组类型,则需要在导出端显式添加注释 [t1, t2]

¥Fix: If a tuple type is expected, then the annotation [t1, t2] needs to be explicitly added on the export side.

导出中的间接对象分配

¥Indirect object assignments in exports

Flow 允许代码

¥Flow allows the code

function foo(): void {}
foo.x = () => {};
foo.x.y = 2;
module.exports = foo;

但在类型优先中,导出的类型将是

¥but in types-first the exported type will be

{
(): void;
x: () => void;
}

换句话说,它不会考虑 y 上的更新。

¥In other words it won’t take into account the update on y.

使固定:要在导出类型中包含 y 的更新,需要使用类型对导出进行注释

¥Fix: To include the update on y in the exported type, the export will need to be annotated with the type

{
(): void;
x: { (): void; y: number; };
};

这同样适用于更复杂的分配模式,例如

¥The same holds for more complex assignment patterns like

function foo(): void {}
Object.assign(foo, { x: 1});
module.exports = foo;

你需要使用 { (): void; x: number } 手动注释导出,或者在函数定义之前进行赋值

¥where you’ll need to manually annotate the export with { (): void; x: number }, or assignments preceding the function definition

foo.x = 1;
function foo(): void {}
module.exports = foo;

请注意,在最后一个示例中,如果静态更新位于定义之后,则 Flow types-first 将获取静态更新:

¥Note that in the last example, Flow types-first will pick up the static update if it was after the definition:

function foo(): void {}
foo.x = 1;
module.exports = foo;

已导出带有更新的变量

¥Exported variables with updates

类型优先签名提取器不会获取导出的 let 绑定变量的后续更新。考虑这个例子

¥The types-first signature extractor will not pick up subsequent update of an exported let-bound variables. Consider the example

let foo: number | string = 1;
foo = "blah";
module.exports = foo;

在经典模式下,导出类型将为 string。在类型优先中,它将是 number | string,因此如果下游类型取决于更精确的类型,那么你可能会收到一些错误。

¥In classic mode the exported type would be string. In types-first it will be number | string, so if downstream typing depends on the more precise type, then you might get some errors.

使固定:在更新中引入一个新变量并导出该变量。例如

¥Fix: Introduce a new variable on the update and export that one. For example

const foo1: number | string = 1;
const foo2 = "blah";
module.exports = foo2;