您发现自己有多少次跟踪某些代码中的错误,只是发现错误是应该避免的简单事情? 也许您以错误的顺序将参数传递给函数,或者您试图传递字符串而不是数字? JavaScript的弱类型系统以及试图将变量强制转换为不同类型的意愿可能是一整类错误的源头,而这些错误在静态类型语言中是不存在的。
2017年3月30日 :文章已更新,以反映对Flow库的更改。
Flow是Facebook于2014年在Scale Conference上首次推出的JavaScript静态类型检查器。Flow的设计目标是发现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
出现配置文件后,您可以通过在终端上运行以下命令来对项目文件夹和任何子文件夹中的代码进行临时检查:
npm run flow check
但是,这不是使用Flow的最有效方法,因为它会使Flow本身每次都重新检查整个项目的文件结构。 我们可以改用Flow服务器。
Flow服务器以增量方式检查文件,这意味着它仅检查已更改的零件。 可以通过在终端上npm run flow
命令npm run flow
来启动服务器。
第一次运行此命令时,服务器将启动并显示初始测试结果。 这允许更快和增量的工作流程。 每次您想知道测试结果时,请在终端上运行flow
。 完成编码会话后,可以使用npm run flow stop
服务器。
Flow的类型检查是可选的 。 这意味着您不需要一次检查所有代码。 您可以选择要检查的文件,然后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()
方法仅适用于string
,因此它期望x
为string
。
可空类型
与其他类型的系统相比,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()
的参数必须为数字。
在这种情况下,我们可以使用类型注释来指定所需的行为。 类型注释的前缀为:
冒号),可以放在函数参数,返回类型和变量声明上。
如果我们在上面的代码中添加类型注释,则其报告如下:
/*@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
是一个数组,其元素应为数字。
班级
下面给出了类和对象的示例架构。 要记住的唯一方面是,我们可以使用|
在两种类型之间执行“或”运算|
符号。 变量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);
对象文字
我们可以使用类似于类的方式来注释对象文字,从而指定对象属性的类型。
/*--------- 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)
}
空值
任何类型的T
可以制成包括null
/ undefined
由写?T
代替T
如下图所示:
/*@flow*/
var foo : ?string = null;
在这种情况下, foo
可以是字符串或null
。
我们只是在这里介绍Flow类型注释系统的表面。 一旦您对使用这些基本类型感到满意,我建议您在Flow网站上深入研究类型文档 。
库定义
我们经常遇到必须在代码中使用第三方库中的方法的情况。 在这种情况下,Flow将引发错误,但是通常情况下,我们不希望看到这些错误,因为它们会分散检查我们自己的代码的注意力。
值得庆幸的是,我们无需触摸库代码即可防止这些错误。 相反,我们可以创建一个库定义(libdef)。 libdef只是一个JavaScript文件的花哨术语,其中包含第三方代码提供的功能或方法的声明。
让我们看一个例子,以更好地了解我们正在讨论的内容:
/* @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对_
变量一无所知。 要解决此问题,我们需要引入Underdef的libdef。
使用流式
值得庆幸的是,有一个名为flow-typed的存储库,其中包含许多流行的第三方库的libdef文件。 要使用它们,您只需要将相关定义下载到项目根目录中名为flow-typed
的文件夹中。
为了进一步简化流程,有一个命令行工具可用于获取和安装libdef文件。 它是通过npm安装的:
npm install -g flow-typed
安装完成后,运行flow-typed install
将检查项目的package.json
文件,并下载libdefs以查找其发现的任何依赖项。
创建自定义libdefs
如果您使用的库在流类型的存储库中没有libdef可用,则可以创建自己的库。 我不会在这里详细介绍,因为您不需要经常这样做,但是如果您有兴趣,可以查看文档 。
剥离类型注释
由于类型注释不是有效的JavaScript语法,因此在浏览器中执行代码之前,我们需要将其从代码中剥离。 可以使用流删除类型工具或作为Babel预设来完成 (如果您已经在使用Babel来转译代码)。 我们将仅在本文中讨论第一种方法。
首先,我们需要安装flow-remove-types作为项目依赖项:
npm install --save-dev flow-remove-types
然后,我们可以在package.json
文件中添加另一个script
条目:
"scripts": {
"flow": "flow",
"build": "flow-remove-types src/ -D dest/",
}
该命令将从src
文件夹中存在的文件中删除所有类型注释,并将编译后的版本存储在dist
文件夹中。 可以像其他JavaScript文件一样将已编译文件加载到浏览器中。
在构建过程中,有几个模块捆绑器可用的插件来剥离注释。
结论
在本文中,我们讨论了Flow的各种类型检查功能以及它们如何帮助我们捕获错误并提高代码质量。 我们还看到了Flow如何通过在每个文件上“选择加入”以及进行类型推断来使入门变得非常容易,这样我们就可以开始获得有用的反馈,而不必在整个代码中添加注释,
您对JavaScript的静态类型检查有何看法? 您认为这有用吗,还是只是另一个不必要的工具,给现代JavaScript带来了更多的复杂性? 本文是否鼓励您亲自检查Flow? 请随时在下面分享您的想法,疑问或评论。
From: https://www.sitepoint.com/writing-better-javascript-with-flow/