文档结构  
翻译进度:已翻译     翻译赏金:10 元     ¥ 我要打赏

你有多少次发现自己在代码中跟踪 Bug 时,只是为了找到那些原本可以简单避免的问题?可能你给一个函数传递参数的顺序错误,又或者你尝试给一个数值参数传递了字符串?JavaScript 的弱类型系统和你强迫使用不同类型变量的习惯是这类错误的根源所在,而这些问题在静态类型语言中是不存在的。

2017年3月30日 更新:根据最新的 Flow 库的变化进行了内容调整。

Flow 是 Facebook 在 2014 年的 Scale Conference 大会上发布的一个 JavaScript 的静态类型检查器。其构想是为了解决 JavaScript 代码中的类型错误,你并不需要对代码进行修改,因此不需要消耗程序员太多精力。与此同时,它还增加了额外的语法来提供更多的控制。

第 1 段(可获 2.03 积分)

在这篇文章中我将向你介绍 Flow 以及其主要特性。我们将了解如何安装,增加类型注解,以及如何自动的在代码运行时剔除注解。

安装

Flow 当前支持 OS X 以及 64位的 Linux 和 Windows 系统。最简单的安装方法是通过 npm 来安装:

npm install --save-dev flow-bin

然后增加如下内容到你项目的 package.json 文件中,位于 scripts 部分:

"scripts": {
  "flow": "flow"
}

完成之后就可以开始体验 Flow 的特性了。

开始使用

第 2 段(可获 1.15 积分)

项目的根目录必须有一个名为 .flowconfig 的配置文件,我们可以用下面命令来创建一个空的配置文件:

npm run flow init

一旦存在配置文件,你就可以在代码中运行专门的检查。通过在项目目录或者任何子目录下运行如下命令:

npm run flow check

然而这并非使用 Flow 最高效的方式,因为这会让 Flow 每次都要重新检查整个项目的文件结构。这时候我们可以使用 Flow 服务器来代替。

Flow 服务器对文件进行增量检查,这意味着它只检查更改的部分。可以通过在命令行中运行 npm run flow 来启动 Flow 服务器。

第 3 段(可获 1.46 积分)

当你首次运行这个命令时,服务器会自动并显示最初的测试结果。这个增量工作流要快得多。每次你想要知道测试结果时,在命令行中运行 flow 即可。在你完成代码方面工作后,你可以使用 npm run flow stop 命令来停止服务器。

Flow 的类型检查是可选的,这意味着你无需一次性检查你所有的代码。你可以选择你想要检查的文件,然后 Flow 会为你完成剩下的工作。这个选择是通过在 JavaScript 文件的顶部放置 Flow 的 @flow 指令来实现的:

第 4 段(可获 1.41 积分)
/*@flow*/

这个特性在你想检查已有的项目时非常有用,你可以选择一个个你想要检查的文件然后找出存在的问题。

类型推导

一般来说,有下列两种类型检查方法:

  • 注解: 我们在代码中指定类型,然后类型检查器会根据你的期望来进行代码的评估
  • 代码推导: 这个工具足够智能,可以通过检查上下文来推导出代码所需的类型

使用注解,我们必须编写一些额外的代码,而这些代码只是在开发的时候有用,并在最终 JavaScript 构建和浏览器执行时被移除。这是通过增加额外的类型注解来让代码可检查。

第 5 段(可获 1.85 积分)

而第二种场景中,代码无需做任何修改就可以进行测试,这降低的程序员的工作量。它不会强迫你更改代码,因为它会自动推导出表达式所需的数据类型。这就是类型推导,这是 Flow 最重要的特性之一。

为了演示这个特性,我们编写了如下示例:

/*@flow*/

function foo(x) {
  return x.split(' ');
}

foo(34);

当你在终端窗口中运行 npm run flow 命令时,上面代码将报错,因为 foo() 函数需要一个字符串参数,而我们传递的是数值类型参数。

第 6 段(可获 1.24 积分)

错误看起来大概如下所示:

index.js:4
  4:   return x.split(' ');
                ^^^^^ property `split`. Property not found in
  4:   return x.split(' ');
              ^ Number

它很清楚的指出错误发送的位置以及原因。当我们将参数从数值类型修改为字符串类型时,错误就消失了,如下代码所示:

/*@flow*/

function foo(x) {
  return x.split(' ');
};

foo('Hello World!');

正如我所说的,上述的代码运行不会出错。这里我们看到的是 Flow 认为 split() 方法只接受字符串参数,因此 x 必须是字符串。

第 7 段(可获 0.94 积分)

允许为 Null 的类型

Flow 使用和其他类型系统不同的方式来处理 null 。它不会忽略 null,虽然在传递 null 时候可能会导致错误,并使得应用崩溃。

看看如下代码:

/*@flow*/

function stringLength (str) {
  return str.length;
}

var length = stringLength(null);

上述场景中,Flow 会抛出一个错误。为了解决这个问题,我们必须单独对 null 进行处理:

/*@flow*/

function stringLength (str) {
  if (str !== null) {
    return str.length;
  }

  return 0;
}

var length = stringLength(null);
第 8 段(可获 0.75 积分)

我们使用了 null 值检查以确保代码可以在任何情况下运行正确。Flow 会认为上面那段代码是可用代码。

类型注解

我上面提到的,类型推导是 Flow 最棒的功能之一,我们不需要编写类型注解就可以得到有用的反馈。但是在某些场景下,在代码中增加注解是必要的,这样可以提供更好的检查并删除那些又歧义的代码。

看看如下代码:

/*@flow*/

function foo(x, y){
  return x + y;
}

foo('Hello', 42);

上述代码中 Flow 不会发现任何错误,因为 + (plus) 操作符可以用于字符串和数值类型,而我们并没有指明说 add() 函数要求的是数值参数。

第 9 段(可获 1.34 积分)

在这里我们使用类型注解来指定想要的行为。类型注解使用 a : (冒号) 可以放在函数的参数、返回类型以及变量的声明。

如果我们为上面代码增加类型注解,就变成了这样:

/*@flow*/

function foo(x : number, y : number) : number {
  return x + y;
}

foo('Hello', 42);

这个代码会报错,因为函数要求的参数类型是数值型,但我们却传递了字符串。

在终端上看到的错误信息如下:

index.js:7
  7: foo('Hello', 42);
         ^^^^^^^ string. This type is incompatible with the expected param type of
  3: function foo(x : number, y : number) : number{
                      ^^^^^^ number
第 10 段(可获 0.94 积分)

如果我们传递一个数值,而不是 'Hello' 这样的字符串,也不会有任何错误。类型注解在庞大复杂的 JavaScript 项目中非常有用,可以确保程序按照你期望的方式运行。

记住上面的例子,接下来我们看看 Flow 支持的不同的类型注解。

函数

/*@flow*/

/*--------- Type annotating a function --------*/
function add(x : number, y : number) : number {
  return x + y;
}

add(3, 4);

上面代码在变量和函数上使用了注解。add() 函数的参数以及返回值要求必须是数值类型。如果传递了其他类型的参数则 Flow 会报错。

第 11 段(可获 1.13 积分)

数组

/*-------- Type annotating an array ----------*/
var foo : Array<number> = [1,2,3];

数组注解的形式是 Array<T> ,其中 T 表示数组元素的类型。在上面代码中,foo 是一个数组,数组元素类型是数值类型。

下面是一个类和对象的示例。唯一需要记住的是我们可以在两个类型中通过 | 符号来执行 OR 操作。bar1 变量被注解必须是 Bar 这个类型。

/*-------- Type annotating a Class ---------*/
class Bar{
  x:string;           // x should be string       
  y:string | number;  // y can be either a string or a number
  constructor(x,y){
    this.x=x;
    this.y=y;
  }
}

var bar1 : Bar = new Bar("hello",4);
第 12 段(可获 0.94 积分)

对象常量

我们可以在类中使用类似的方式来注解对象常量,指定类属性的类型。

/*--------- Type annonating an object ---------*/

var obj : {a : string, b : number, c: Array<string>, d : Bar} = {
  a : "hello",
  b : 42,
  c : ["hello", "world"],
  d : new Bar("hello",3)
}

Null

任意类型 T 都可以通过编写 ?T 来包含 null/undefined ,如下所示:

/*@flow*/

var foo : ?string = null;

在这里,foo 可以是字符串或者是 null。

到这里我们只是简单的对 Flow 的类型注解系统进行简单的介绍。一旦你熟悉这些基本类型的使用,我建议你阅读 Flow 网站里的 类型文档 。

第 13 段(可获 0.98 积分)

库定义

我们经常需要在我们的代码中使用第三方库的方法。Flow 会在这种情况下报错,但是通常我们并不想看到这些错误,它们不应该在代码检查的范围内。

好消息是我们并不需要去碰这些第三方库的代码来避免这些错误。我们可以创建一个库定义 (libdef) 。对 JavaScript 文件而言,libdef 是一个花哨的术语,包含了第三方代码中提供的方法和函数的声明。

下面来看个例子以便于我们更好的理解 libdef:

第 14 段(可获 1.3 积分)
/* @flow */

var users = [
  { name: 'John', designation: 'developer' },
  { name: 'Doe', designation: 'designer' }
];

function getDeveloper() {
  return _.findWhere(users, {designation: 'developer'});
}

上面代码将会报如下错误:

interfaces/app.js:9
  9:   return _.findWhere(users, {designation: 'developer'});
              ^ identifier `_`. Could not resolve name

错误产生的原因是 Flow 对  _ 变量一无所知。要解决这个问题需要为 Underscore 引入 libdef 。

使用 flow-typed

值得庆幸的是,有一个 flow-typed 项目包含了 libdef 文件,可用于很多流行的第三方库。要使用它,只需要简单的下载相关文件到你项目目录下名为 flow-typed 的文件夹。

第 15 段(可获 0.9 积分)

为了进一步简化流程,Flow 提供了一个命令行工具来获取和安装 libdef 文件,通过 npm 执行:

npm install -g flow-typed

一旦安装完毕,需要运行 flow-typed install 来检查项目的 package.json 文件并下载 libdef 的依赖。

创建自定义的 libdefs

如果你在 flow-typed 仓库用的库不包含 libdef,你需要自己创建一个。在这里我不做详细叙述,这些也不是你经常要做的工作,但如果你有兴趣的话,可以阅读 这个文档

第 16 段(可获 1.21 积分)

剥离类型注解

由于类型注解并不使用有效的 JavaScript 语法,因此我们需要将它们从代码中剥离,以便在浏览器中执行。这个可以通过使用 the flow-remove-types tool 或者是 Babel preset 工具来实现,如果你已经用了 Babel 来转译代码的话。本文中我们只讨论第一种方法。

首先我们需要先安装 flow-remove-types 作为项目的依赖:

npm install --save-dev flow-remove-types

接下来在 package-json 文件中的 script 部分增加下面内容:

"scripts": {
  "flow": "flow",
  "build": "flow-remove-types src/ -D dest/",
}
第 17 段(可获 0.98 积分)

这个命令将 src 目录中所有的 JavaScript 文件里的注解剥离出来,并在 dist 目录中存放编译完的结果。编译后的文件可以直接在浏览器中执行,跟普通的 JavaScript 文件没有区别。

还有很多 several module bundlers 的可用插件可以在构建过程中对注解进行剥离。

结论

本文中我们讨论了 Flow 的不同类型检查功能以及如何帮助我们捕获错误来提升代码的质量。我们同时也看到了 Flow 简化了文件的类型推导。我们可以利用它来得到有用的反馈,而且不需要对现有代码做任何的修改。

第 18 段(可获 1.51 积分)

你对 JavaScript 的静态类型检查是怎么看的呢?这篇文章是否对你有用呢?或者只是另外一个给 JavaScript 带来更多复杂度的没什么必要的工具而已呢?别忘了分享你的想法和疑问,在评论里给我们建议,谢谢。

第 19 段(可获 0.63 积分)

文章评论