时间:2021-04-21 09:37:07 | 栏目:vue | 点击:次
你是否经常在debug那些简单可避免的bug?可能你给函数传参的时候搞错了参数的顺序,或者本来应该传个Number类型的参数,你传了一个String类型?JavaScript的弱类型是这一类bug的罪魁祸首,静态类型语言中不存在此类bug。Flow就是JavaScript的静态类型检查工具,该库的目标在于检查JavaScript中的类型错误,开发者通常不需要修改代码即可使用,故使用成本很低。同时,它也提供额外语法支持,使得开发者能更大程度地发挥Flow的作用。
一、flow的安装
flow可以直接通过npm或者yarn安装。
这里以npm为例:npm install --save-dev flow-bin
安装完成后在package.json中加入下面的脚本
"scripts": { "flow":"flow check" }
同时还要安装babel编译器,将flow的类型检查代码从代码中剥离,转变成正常的js代码
npm install --save-dev babel-cli babel-preset-flow
在babel配置文件.babelrc中加入
{ "presets": ["flow"] }
二、flow使用
1、配置flow
npm run flow init生成flow配置文件.flowconfig
[ignore] .*/node_modules/.* .*/test/.* .*/build/.* .*/config/.* [include] [libs] [options] module.file_ext=.vue module.file_ext=.js
[ignore]:Flow 默认检查项目目录下所有文件,但是有很多文件必定是我们不想检查的,就像 node_modules、build 等目录下的文件,所以我们需要在将这些目录写在 ignore 配置下。
[include]:所谓的项目目录其实是 .flowconfig 目录,并不是真正的项目目录,如果我们在这个项目中的某个目录下创建一个 .flowconfig,那么 .flowconfig 所在的目录也会变为一个 Flow 项目。那么,如果我们想对当前 Flow 项目以外的文件或者目录进行检查,需要把它们写在 include 配置项中。
[libs]:在项目中,我们可能会用到很多自定义的类型,比如说要记录对象的结构,它可能在每个文件中都会被运用到,我们将其抽取为全局的类型或数据结构,在任何文件都可以使用。为了管理方便,我们将全局类型都定义在一个或多个单纯的目录中统一管理。这里存放的有可能是一个定义好的数据结构,存放的也有可能是根据项目中某个类对应的数据类型。我们将这些文件或目录写在 libs 配置项中,这个配置对于我们使用 Flow 来说很重要。
[options]:这里填写对 Flow 项目的一些配置,配置项以key=value的形式,每行写一个。所有的配置见官方文档
[lints]:官网中没有提到 lints 相关的配置
2、新建一个文件index.js
// @flow let a:number = '3'; // @flow或者 /* @flow */,告诉flow检查这个文件
如果不愿意那么麻烦,想检查全部文件,那么可以修改配置文件.flowconfig,在[options]配置项中添加 all=ture。
[ignore] [include] [libs] [options] all=true [lints]
[libs] 配置项中的文件不需要添加// @flow,它们都将被检查。
注:在vue单文件组件使用flow需要额外配置:
(1)在.flowconfig文件的[options]中配置.vue文件扩展名,module.file_ext=.vue
(2)在.vue文件中需注释掉template script styled标签
输入npm run flow
执行类型检查。
注:完成设置之后,在终端输入以下命令可以在你的项目根目录以及任何子目录文件夹下进行专门的类型检查:npm run flow check,但是,这并不是最高效的使用方式,因为每次Flow都会重新检查整个项目的所有文件。开发过程中,推荐启动Flow服务:Flow服务的工作方式是增量检查,也就是说它只检查变化的部分。在终端输入以下命令来启动Flow服务:npm run flow,首次运行该命令时,服务启动并且显示最初类型检查结果。这保证了Flow更高效的增量式工作流。然后接下来每次想要知道检测结果,只要输入flow命令即可。开发结束之后,输入npm run flow stop停止服务。
Flow的类型检查是可选的,并不需要一次性检查所有代码。你可以选择你想要检查的文件,只要在对应的JavaScript文件最前面加上带有@flow标识的注释即可:/*@flow*/,当你想在已有项目中加入Flow的时候,该特性特别有帮助。因为你可以一一选择并检测你要的文件,然后修正错误。
三、类型推断
通常,类型检查分为以下两种方式:
通过注释:事先注释好我们期待的类型,Flow就会基于这些注释来评估
通过代码推断:通过变量的使用上下文来推断出变量类型,然后根据这些推断来检查类型
第一种方式,我们需要额外编写只在开发阶段起作用的代码,最后在代码编译打包的阶段被剔除。显然,这种额外添加类型注释的方式增加了工作量。
第二种方式,不需要任何代码修改即可进行类型检查,最小化开发者的工作量。它不会强制你改变开发习惯,因为它会自动推断出变量的类型。这就是所谓的类型推断,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
上述信息清楚地指出了出错位置和错误原因。我们只要将参数变成字符串,即可修正错误。该例想说明的是,因为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);
现在Flow就能检查出错误,因为函数参数的期待类型为数字,而我们提供了字符串。Flow报错信息类似如下:
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
如果传入的参数是数字,就不会有错误。类型注释在大型复杂的JavaScript文件中也很有用,它能保证代码按照预期进行。
六、Flow能支持的其他更多类型注释。
其实说到底就是类似java那种强语言类型的写法,给每个变量声明是什么类型,给每个函数声明返回值类型,给每个数组元素声明类型等,就是仿造java的写法,哈哈,java传值不对时就会给你报错一样的道理
1、函数
/*@flow*/ function add(x : number, y : number) : number { return x + y; } add(3, 4);
上述代码展示了变量类型注释以及函数类型注释。函数add()的参数,以及函数的返回值,期待类型为数字。如果传入其他类型参数,Flow就会检测到错误。
2、数组
var foo : Array<number> = [1,2,3];
数组类型注释的格式是Array<T>,T表示数组中每项的数据类型。在上述代码中,foo是每项均为数字的数组。
3、类
下面展示了类和对象的类型注释模型。唯一需要注意的是,可以在两个类型之间使用或逻辑,用|来间隔。变量bar1添加了必须为Bar类的类型注释。
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);
4、对象字面量
对象的类型注释类似于类,指定对象属性的类型。
var obj : {a : string, b : number, c: Array<string>, d : Bar} = { a : "hello", b : 42, c : ["hello", "world"], d : new Bar("hello",3) }
5、Null
若想任意类型,T可以为null或者undefined,只需类似如下写成 ?T 的格式即可。
/*@flow*/ var foo : ?string = null;
此时,foo可以为字符串,也可以为null。
目前我们只对Flow的类型注释做了很浅的探索。一旦你习惯了使用这些基本类型,建议在Flow官网上的类型文档深入了解所有的类型。
七、库定义
我们经常需要引入第三方库,Flow检查时就会抛出错误。但这并不是我们期待的错误。
庆幸的是,我们不需要修改库源码去防止这些报错。我们只需创建一个库定义(libdef)。libdef是包含第三方库声明的JS文件简称。观察下面的例子:
/* @flow */ var users = [ { name: 'John', designation: 'developer' }, { name: 'Doe', designation: 'designer' } ]; function getDeveloper() { return _.findWhere(users, {designation: 'developer'}); }
Flow会检查出以下错误:
interfaces/app.js:9
9: return _.findWhere(users, {designation: 'developer'});
^ identifier `_`. Could not resolve name
由于Flow并不认识_,所以会报错。要解决这个问题,我们需要引入Underscore的库定义
1、使用flow-typed
flow-typed仓库包含了众多流行的第三方库的libdef。只需在项目根目录下创建一个名为flow-typed的文件夹,并且下载相关的定义文件即可。为了进一步简化,可以用npm的命令行方式一键获取和安装libdef文件:npm install -g flow-typed
安装成功之后, 运行flow-typed install来检查package.json文件,并且下载所有项目中用到的第三方库的libdef。
2、自定义libdef
如果你用的库并不在flow-typed仓库,你可以创建你自己的libdef。本文不会细谈自定义libdef,因为很少会有人遇到,感兴趣可以查看此文档。
八、剔除类型注释
由于额外添加的类型注释不是正确的JavaScript语法,打包编译的时候需要在源码中剔除。可以通过flow-remove-types来剔除,或者如果你已经用Babel来转译JS,你可以使用Babel preset来移除。我们只讨论第一种方法。
首先需要安装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文件。