你是不是常常发现自己在跟踪代码中的一个 bug,最后发现的错误只是某些本应该可以避免的简单问题呢? 可能你只是按照错误的顺序传递了参数,或者也许是你在尝试传递一个字符串而不是一个数字?JavaScript 的弱类型系统,以及试着将变量在不同类型间强制转换的意图可能就是一整类 bug 的来源,而这些 bug 并不会存在于静态类型语言之中。
Flow 是一个用于 JavaScript 的静态类型检查器,最早由 Facebook 在 2014 年的 Scale 大会 上推出。它的目标是不需要开发人员去频繁的修改实际代码,就能找到 JavaScript 代码中的类型错误,从而大大节省这方面时间与精力的消耗。同时,它也向 JavaScript 增加了额外的语法,以提供给开发者更多的控制能力。
在本文中,我会向你介绍 Flow 以及它的主要特性。我们将介绍如何设置它,如何在代码中添加类型注释,以及如何在运行代码时自动删除这些注释。
Flow 目前可运行于 Mac OS X, Linux (64 位) 以及 Windows (64 位) 系统上。安装它最简单的办法就是通过 npm:
- npm install --save-dev flow-bin
然后将它添加到工程的 package.json 文件, 就在 scripts 部分下面:
- "scripts": {
- "flow": "flow"
- }
完成了此项工作,我们就做好了继续探索其功能的准备。
一个被命名为 .flowconfig 的配置文件必须被放在工程文件的根目录之下。通过运行如下命令,我们可以创建出一个空的配置文件:
- npm run flow init
放好了这个配置文件,你就能在终端上执行如下命令,来在工程文件夹或者任意子目录下的代码上运行 ad-hoc 检查了:
- npm run flow check
不过,这并非使用 Flow 的最有效方式,因为这样做会导致 Flow 每次都会重新检查整个工程文件结构。所以我们可以使用 Flow 服务器。
Flow 服务器会对文件进行增量检查,意思就是它只会检查被修改了的部分。服务器可以通过在终端上执行 npm run flow 命令来启动。
在你第一次运行该命令的时候,服务器将会启动并显示初始的测试结果。该操作运行很快并且是增量的工作流。每次在你想要知道测试结果的时候,就在终端上运行 flow。完成了编码环节之后,可以使用 npm run flow stop 来终止服务器的运行。
Flow 的类型检查是 opt-in 的。这就意味着你无须一次性完成对所有代码的检查。Flow 只会为你检查那些你想要检查的那些文件,怎么让 Flow 知道你想要检查哪些文件呢?只需要在这些 JavaScript 文件顶部添加 @flow 作为注释就可以了:
- /*@flow*/
这有利于将 Flow 集成到一个现有工程中,因为你可以选择你想要检查的文件,然后逐步解决所发现的问题。
通常,类型检查有以下两种方法:
使用注解的话,我们就得编写一些额外的代码,它们只会在开发期间被用到,最终在浏览器加载的 JavaScript 构建版本中被去掉。这样做会增加一些额外的工作,也就是通过加入额外的类型注解来让代码 "听话"。
而在第二个场景中,代码随时 "待命", 故而缩减了程序员的工作量。我们不需要对代码进行修改,因为它会自动地推断出表达式的数据类型。这就是 类型推断 , 它是 Flow 最重要的特性之一。
为了描述该特性,我们来看看如下示例:
- /*@flow*/
- function foo(x) {
- return x.split(' ');
- }
- foo(34);
这段代码会在你运行 npm run flow 命令的时候在终端上报错, 因为函数 foo() 需要的是一个字符串作为参数,而我们传入的却是一个数字。
报错提示大概如下:
- 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 就必须是一个字符串。
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);
为了保证代码在所有的情况下都能正常运行,我们专门针对 null 进行检查。Flow 就会将最后的那行代码当做是有效的代码。
如我在上面所提到过的,类型推断是 Flow 最好的特性之一,因为无需编写类型注解我们就可以得到有效的反馈。不过,在某些情况下,有必要向代码添加注解以更好地检查和消除不确定性。
看看下面的代码:
- /*@flow*/
- function foo(x, y){
- return x + y;
- }
- foo('Hello', 42);
Flow 在上面的代码中不会发现任何错误,因为 + (加) 操作可以被用在字符串和数字之上, 而我们并没有将 add() 的参数指定为数字。
在这种情况下,我们可以使用类型注解来指定想要的行为。Type 注解前缀一个 : (冒号),可以被放在函数参数、返回类型以及变量声明之上。
如果我们向上面的代码添加了类型注解,那么它就将如下所示:
- /*@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
如果我们传入的是一个数字而非 "Hello",就不会报错了。类型注解对在庞大且复杂的 JavaScript 文件中指定想要的行为也同样有用。
基于前面的例子,让我们来看看 Flow 支持的一些其它的类型注解。
- /*@flow*/
- /*--------- Type annotating a function --------*/
- function add(x : number, y : number) : number {
- return x + y;
- }
- add(3, 4);
上面的代码展示了一个变量和一个函数的注解。add() 函数的参数以及返回值都被期望是数字的。如果传入了任何其它的数据类型, Flow 就会抛出一个错误。
- /*-------- 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);
来源: http://www.open-open.com/lib/view/open1491445521943.html