1. 什么是Flow?javascript
Flow 是javascript代码的静态类型检查工具。它是Facebook的开源项目(https://github.com/facebook/flow),Vue.js(v2.6.10的源码使用了Flow作了静态类型检查。所以咱们如今先来了解下Flow的基本知识,有助于咱们分析源码。
2. 为何要用Flow?java
javascript是弱类型语言,弱类型体如今代码中的变量会根据上下文环境自动改变的数据类型。那么这种弱类型有优势也有缺点,优势是咱们容易学习和使用,缺点是:开发者常常由于赋值或传值致使类型错误。形成一些和预期不同的结果。在代码编译的时候可能不会报错,可是在运行阶段就可能会出现各类奇怪的bug。所以在大型项目中咱们有必要使用Flow来作代码静态类型检查。node
下面咱们从一个简单的demo提及。好比以下代码:git
function add (x) { return x + 10; } var a = add('Hello!'); console.log(a); // 输出:Hello!10
如上代码,x这个参数,咱们在add函数声明的时候,其实咱们但愿该参数是一个数字类型,可是在咱们代码调用的时候则使用了字符串类型。致使最后的结果为 "Hello!10"; 为何会出现这种结果呢?那是由于 加号(+)在javascript语言中,它既有做为数字的加运算符外,还能够做为字符串的拼接操做。github
所以为了解决类型检查,咱们可使用Flow来解决。下面咱们来介绍下 如何在咱们项目中使用Flow。npm
3. 开始一个新的 Flow 项目编程
首先咱们建立一个名为 v-project 项目:json
$ mkdir -p v-project
$ cd v-project
接着,添加Flow, 执行以下命令:bootstrap
$ npm install --save-dev flow-bin
如上安装完成后,咱们须要在要执行静态检查文件的根目录下 执行一下命令:flow init.执行完成后,咱们会发现咱们根目录下多了一个 .flowconfig 文件。该文件的做用是: 告诉Flow在这个目录下文件开始检测。咱们能够在该 .flowconfig 配置文件内能够进行一些高级配置,好比说仅包含一些目录, 或 忽略一些目录进行检测等操做。数组
如今在咱们的项目下会有以下目录结构:
|--- v-project | |--- node_modules | |--- .flowconfig | |--- package.json
package.json 文件基本代码以下:
{ "name": "v-project", "devDependencies": { "flow-bin": "^0.106.3" } }
如今咱们在 v-project根目录下新建 index.js 文件,代码以下:
// @flow var str = "hello world!"; console.log(str);
接着咱们在项目的根目录下运行以下命令,若是一切正常的话,会提示以下信息:
$ flow check
Found 0 errors
可是若是咱们把代码改为以下所示; 它就会报错了,index.js 代码改为以下:
// @flow var str != "hello world!"; console.log(str);
执行结果以下图所示:
注意第一行,咱们添加了 // @flow, 是用来告诉 Flow,你须要检查我这个文件。若是不加这个注释,Flow就不会检查该文件了。
固然,咱们能够强制 Flow 来检测全部的文件,无论文件有没有 @flow 注释,咱们只须要在命令行中带上 --all 参数就好了,以下所示:
$ flow check --all
可是这个命令,咱们通常状况下仍是须要慎用的,当咱们在一个大型项目中,该项目假如引入了不少第三方库,那么检测器可能会找到不少咱们不想要的错误。
注意:flow check 这个命令虽然是可行,但不是最高效的用法,该命令会让flow每次都在项目下检查全部文件一遍。
4. 理解类型注释
Javascript是一种弱类型语言,在语法上没有规定明确的表示类型,好比以下JS代码运行是正常的。
function add(num1, num2) { return num1 + num2; } var result = add(1, '2'); console.log(result); // 输出:12
如上代码,输出的 result 的值为 '12'; 可是有可能这并非咱们想要的,咱们有可能想要两个数字相加得出结果,可是编写代码的时候,一不当心把参数写成字符串去了。致使预期的结果不同。
Flow 能够经过静态分析和类型注释,来帮咱们解决相似的问题,让咱们的代码更加符合预期。
类型注释通常都是以 : 开头的,可使用在方法参数中、变量声明及返回值中,好比使用类型注释更改上面的代码以下:
// @flow function add(num1:number, num2:number) :number { return num1 + num2; } var result = add(1, '2');
执行命令后结果以下所示:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:8:21 Cannot call add with '2' bound to num2 because string [1] is incompatible with number [2]. [2] 4│ function add(num1:number, num2:number) :number { 5│ return num1 + num2; 6│ } 7│ [1] 8│ var result = add(1, '2'); 9│ console.log(result); Found 1 error
如上代码,num1:number 和 num2:number 的含义是:num1 和 num2 传递的参数都为数字类型的,:number {} 中的 :number的含义是:但愿返回结果也是数字类型。上面若是咱们把 '2' 改为 数字 2 就正常了。
类型注释在大型复杂的项目文件中颇有用,它能保证代码按照预期进行。
下面咱们来看看Flow能支持的其余更多类型注释,分别为以下:
函数
// @flow function add(num1:number, num2:number) :number { return num1 + num2; } add(1, 2);
数组
// @flow var foo : Array<number> = [1, 2, 3];
如上数组类型注释的格式是 Array<number>,number的含义表示数组中的每项数据类型都为 number(数字) 类型。
类
下面是类和对象的注释模型,在两个类型以前咱们可使用 或(|) 逻辑,变量foo添加了必须为Foo类的类型注释。
// @flow class Foo { x: string; // x 必须为字符串类型 y: string | number; // y 能够为字符串或数字类型 constructor(x, y) { this.x = x; this.y = y; } } // 类实例化 var foo : Foo = new Foo("hello", 112);
对象字面量
对象字面量须要指定对象属性的类型便可。以下演示:
// @flow class Foo { x: string; // x 必须为字符串类型 y: string | number; // y 能够为字符串或数字类型 constructor(x, y) { this.x = x; this.y = y; } } var obj : {a : string, b : number, c : Array<string>, d : Foo} = { a : "kongzhi", b : 1, c : ["kongzhi111", "kongzhi222"], d : new Foo("hello", 1) }
Null
假如咱们想任意类型 T 能够为null或undefined的话,咱们只须要相似以下写成 ?T 的格式的便可。
// @flow var foo : ?string = null;
如上代码,foo 能够为字符串,也能够为null。
5. 理解模块界限
在跨模块使用的时候,Flow须要明确注释,为了保证Flow在各自模块内的独立检测,提升性能,所以咱们须要在每一个模块中有本身的Flow.
在咱们的 v-project 项目目录中新建一个 module.js, 整个目录结构假如变为以下:
|--- v-project | |--- node_modules | |--- index.js | |--- module.js | |--- .flowconfig | |--- package.json
module.js 代码以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } module.exports = module;
index.js 代码以下:
/* * index.js * @flow */ var module = require('./module'); var result = module(1122);
在命令行中运行发现报错,以下提示:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:8:21 Cannot call module with 1122 bound to str because number [1] is incompatible with string [2]. index.js 5│ */ 6│ 7│ var module = require('./module'); [1] 8│ var result = module(1122); 9│ module.js [2] 7│ function module(str: string) : number { Found 1 error
若是咱们把 index.js 代码中的 module(1122); 改为 module('1122'); 字符串这样的,再运行下就不会报错了。
6. 使用Flow检测第三方库模块
大多数javascript应用程序都依赖于第三方库。若是在咱们的项目代码中引用外部资源时,咱们要如何使用Flow呢?庆幸的是,咱们不须要修改第三方库源码,咱们只须要建立一个库定义 (libdef). libdef是包含第三方库声明的JS文件的简称。
下面咱们来演示下这个过程,假如咱们选择了 lodash 库。下面咱们的 index.js 代码中使用了该库。以下代码所示:
// @flow import _ from 'lodash';
而后咱们在命令行运行的时候 会报错以下信息:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:4:15 Cannot resolve module lodash. 1│ 2│ // @flow 3│ 4│ import _ from 'lodash'; 5│ 6│ 7│ Found 1 error
这是由于flow 找不到 lodash 模块,所以咱们这个时候须要去下载 lodash 的模块文件,咱们可使用 flow-typed 来管理这些第三方库的定义文件。
1. flow-typed
flow-typed 仓库包含了不少流行的第三方库的定义文件。 flow-typed 能够看github代码(https://github.com/flow-typed/flow-typed)
咱们使用npm命令行方式全局安装下 flow-typed, 以下命令:
npm install -g flow-typed
安装成功后,咱们须要查找该库,是否存在咱们的 flow-typed 仓库中,以下命令查找下:
flow-typed search lodash;
运行命令完成后,咱们就能够看到有以下版本的了。
Found definitions: ╔═══════════╤═════════════════╤══════════════════════╗ ║ Name │ Package Version │ Flow Version ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash-es │ v4.x.x │ >=v0.104.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash-es │ v4.x.x │ >=v0.63.x <=v0.103.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.47.x <=v0.54.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.38.x <=v0.46.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.55.x <=v0.62.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.63.x <=v0.103.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.104.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.28.x <=v0.37.x ║ ╚═══════════╧═════════════════╧══════════════════════╝
如今咱们能够选择一个版本进行安装,咱们须要在咱们的项目根目录下运行以下命令:
flow-typed install lodash@4.x.x;
文件下载完成后,会自动在咱们的项目根目录下 新建一个 flow-typed/npm 文件夹,在该文件夹下有一个 lodash_v4.x.x.js文件。
那么这个时候,咱们再运行 flow check; 命令就不会报错了。
2. 自定义libdef
若是咱们用的库在flow-typed仓库搜索不到怎么办?好比我引入了一个在flow-typed管理库中找不到的库,好比该库叫 "kongzhi" 库(可是在npm包中确实有该库),该库下有对外暴露的方法,好比叫 findWhere 这样的方法,咱们在 index.js 中调用了该方法,而且该库的假如别名对外叫_; 以下代码:
// @flow var obj = [ { title: 'kongzhi1111', flag: true }, { title: 'kongzhi2222', flag: false } ]; function test() { return _.findWhere(obj, {flag: true}); }
所以 运行 flow check; 命令后,会报以下错误:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:10:10 Cannot resolve name _. 7│ ]; 8│ 9│ function test() { 10│ return _.findWhere(obj, {flag: true}); 11│ } 12│ 13│ Found 1 error
如上代码报错,那是由于Flow不认识全局变量 _. ,要解决这个问题,咱们须要为咱们的 kongzhi库建立一个接口文件。
所以咱们须要在咱们项目中根目录下新建一个叫 "interfaces" 文件夹,在该文件夹下新建一个 'kongzhi.js' 文件,在该文件下代码以下所示:
declare class Kongzhi { findWhere<T>(list: Array<T>, properties: {}) : T; } declare var _: Kongzhi;
而后咱们须要在咱们的 根目录中的 .flowconfig 文件中配置 [libs] 为 interfaces/ 了, 以下所示:
[ignore] [include] [libs] interfaces/ [lints] [options] [strict]
如上,在 .flowconfig 中默认有如上配置项。如上配置后,Flow就会查找 interfaces/目录下的全部 .js 文件做为接口定义。
有了该接口文件,咱们在命令中再次运行下 就不会报错了。以下运行结果:
$ flow check
Found 0 errors
如今整个项目的目录结构变为以下:
|--- v-project | |--- flow-typed | | |--- npm | | | |--- lodash_v4.x.x.js | |--- interfaces | | |--- kongzhi.js | |--- node_modules | |--- .flowconfig | |--- index.js | |--- module.js | |--- package.json
更多的自定义 libdef,请查看(https://flow.org/en/docs/libdefs/creation/)。
7. 剔除类型注释
类型注释不是咱们JS规范的一部分,所以咱们须要移除它,这里咱们使用Babel来移除它。
好比咱们的 module.js 中的代码,以下代码:
function module(str: string) : number { return str.length; }
str: string 和 : number 它不是咱们的JS规范中的一部分,所以无论咱们在浏览器端仍是在nodeJS中运行都会报错的。
为了简单的来测试下,咱们在node.js中运行测试下,以下:
$ node module.js function module(str: string) : number { ^ SyntaxError: Unexpected token : at new Script (vm.js:80:7) at createScript (vm.js:264:10) at Object.runInThisContext (vm.js:316:10) at Module._compile (internal/modules/cjs/loader.js:670:28) at Object.Module._extensions..js (internal/modules/cjs/loader.js:718:10) at Module.load (internal/modules/cjs/loader.js:605:32) at tryModuleLoad (internal/modules/cjs/loader.js:544:12) at Function.Module._load (internal/modules/cjs/loader.js:536:3) at Function.Module.runMain (internal/modules/cjs/loader.js:760:12) at startup (internal/bootstrap/node.js:303:19)
如上能够看到,会报错,在编程时,咱们但愿使用Flow对类型检查,可是在代码运行的时候,咱们须要把全部的类型约束要去掉。所以咱们须要使用Babel这个工具来帮咱们去掉。
所以首先咱们要安装babel相关的库,安装命令以下:
npm install --save-dev babel-cli babel-preset-flow
babel-cli: 只要咱们要安装babel的话,那么babel-cli库都须要安装的。
babel-preset-flow: 该库目的是去除类型。
安装完成后,咱们的 package.json 变成以下:
{ "name": "v-project", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-flow": "^6.23.0", "flow-bin": "^0.106.3" }, "scripts": { "flow": "flow check" } }
为了项目结构合理及方便,咱们把上面的 index.js 和 module.js 放到 src/js 目录里面去,所以目录结构变成以下:
|--- v-project | |--- flow-typed | | |--- npm | | | |--- lodash_v4.x.x.js | |--- interfaces | | |--- kongzhi.js | |--- node_modules | |--- .flowconfig | |--- src | | |--- js | | | |--- index.js | | | |--- module.js | |--- package.json | |--- .babelrc
src/js/module.js 在剔除以前代码以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } module.exports = module;
如上安装完成相应的库以后,咱们须要在项目的根目录下新建 .babelrc 文件,添加以下配置:
{ "presets": ["flow"] }
咱们如今在项目的根目录下运行以下命令,就能够在项目的根目录生成 dist/js 文件夹,该文件夹下有index.js和module.js文件,以下命令:
./node_modules/.bin/babel src -d dist
而后咱们查看 dist/js/module.js 代码变成以下:
/* * module.js * */ function module(str) { return str.length; } module.exports = module;
咱们也能够在node命令行中测试:$ node dist/js/module.js 后也不会报错了。
8. Flow类型自动检测
如上咱们虽然使用了Babel去除了Flow的类型注释,可是咱们并无对Flow的静态类型检测。所以若是咱们想让babel进行Flow的静态类型校验的话,那么咱们须要手动集成另一个插件--- babel-plugin-typecheck。
想了解更多相关的知识, 能够看npm库(https://www.npmjs.com/package/babel-plugin-typecheck)。
接下来咱们须要进行具体的babel集成步骤,所以咱们须要安装 babel-plugin-typecheck 插件。以下命令:
npm install --save-dev babel-plugin-typecheck
固然咱们要全局安装下 babel-cli; 以下命令:
npm install -g babel-cli
接下来,咱们须要在咱们的 .babelrc 中添加 typecheck 插件进去,以下所示:
{ "presets": ["flow"], "plugins": ["typecheck"] }
如今咱们就可使用babel来编译咱们的Flow代码了。以下命令所示:
$ babel src/ -d dist/ --watch src/js/index.js -> dist/js/index.js src/js/module.js -> dist/js/module.js
如今咱们把 src/js/module.js 中的代码改为以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } var str != "hello world!"; console.log(str); module.exports = module;
而后咱们保存后,在命令行中会看到以下报错信息:
能够看到,咱们使用babel就能够完成了校验和编译的两项工做。不再用使用 flow check; 这样的对全局文件进行搜索并检测了。
咱们使用了 babel 的 --watch 功能解决了以前 Flow命令不能同时监听,提示的缺憾了。
babel的缺陷:
可是使用 babel 也有缺陷的,好比咱们如今把 src/js/module.js 代码改为以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } function foo(x : number) :number { return x * 12; } foo('a'); module.exports = module;
如上代码,咱们添加了一个foo函数,有一个参数x,咱们但愿传递的参数为 number 类型,而且但愿返回的值也是 number 类型,可是咱们在 foo('a'); 函数调用的时候,传递了一个字符串 'a' 进去,babel 在检测的时候并无报错,这或许是它的缺陷。
可是咱们在 flow check; 就会报错以下:
9. 了解 .flowconfig 配置项
咱们在项目中的根目录运行命令:flow init; 会建立 .flowconfig文件,该文件的做用是告诉Flow在这个目录下开始检测。不过 .flowconfig配置项也提供了一些配置选项,告诉Flow哪些文件须要检测,哪些文件不须要检测。
.flowconfig 默认有以下配置(咱们讲解前面三个比较经常使用的配置项):
[ignore]
[include]
[libs]
[lints]
[options]
[strict]
1. [ignore] 该配置是用来告诉flow哪些文件不须要检测,默认为空,全部的文件须要检测。咱们也可使用正则去匹配路径,哪些路径不须要进行检测。
[ignore]
.*/src/*
如上表示的含义是:在src目录下的全部文件不须要检测,所以若是src下有某个js文件是不对的类型,也不会报错的。
2. [include] 该配置是用来告诉Flow还要检测哪些文件或者目录。该配置的每一行表示一个待检测的路径,咱们可使用相对于根目录下的路径,或者绝对路径,或支持一个或多个星号通配符。好比以下:
[include] ../xx.js ../xxxDir/ ../xxxDir/*.js
注意:若是 [ignore] 和 [include] 同时存在,而且同时匹配同个路径,那就看那个配置在后面,那个优先级就更高。
3. [libs]
该配置下通常存放第三方接口文件,当咱们引用第三方库文件后,咱们须要声明一个接口文件,咱们能够放在该目录下。好比:
[libs]
interfaces/
Flow就会查找 interfaces/目录下的全部 .js 文件做为接口文件的定义。
如上就是Flow一些基本的知识了解下便可。