vue Series --- recognize Flow (a)

1. What is Flow?

Flow is the javascript code static type checking tool. It is Facebook's open source project (https://github.com/facebook/flow),Vue.js(v2.6.10 source using Flow do static type checking. So now we start to understand the basics under the Flow, It helps us analyze source code.
2. Why use Flow?

javascript is a weakly typed language, weakly typed variables embodied in the code the data type will be changed automatically according to context. Then this weak type has advantages and disadvantages, the advantages of our easy to learn and use, the disadvantage is: Developers often because the assignment or transfer value causes a type error. And it is expected to cause some different results. It may not be an error in the code compile time, but it may appear all sorts of strange bug in the operating phase. Therefore, large-scale projects, we need to use the code to do the Flow static type checking.

Let's start with a simple demo. For example the following code:

function add (x) {
  return x + 10;
}

var a = add('Hello!');
console.log(a); // 输出:Hello!10

As the code, x This parameter, when we add the function declaration, in fact, we hope that this parameter is a numeric type, but when we use a code calls the string type. Leading to the final result is "Hello 10!"; Why is there such a result? That is because the plus sign (+) in javascript language, as it has both digital plus operator, but also as a string of splicing operation.

Therefore, in order to solve the type checking, we can use Flow to resolve. Here we introduce how to use Flow under our project.

3. Start a new project Flow

First, we create a project called v-project:

$ mkdir -p v-project  
$ cd v-project

 Next, Flow, run the following command:

$ npm install --save-dev flow-bin

After the above installation is complete, we need to execute the following command in the root directory of the file you want to perform static checking:. Flow init execution is completed, we will find more than a .flowconfig file in the root directory of us. The role of the file is: Tell Flow file starts to detect in this directory. We can make some advanced configuration in the .flowconfig configuration file, for example, contains only some directories, or ignore some directories for testing and other operations.

Now in our project will have the following directory structure:

|--- v-project
| |--- node_modules
| |--- .flowconfig
| |--- package.json

package.json file base code is as follows:

{
  "name": "v-project",
  "devDependencies": {
    "flow-bin": "^0.106.3"
  }
}

Now we index.js new file in the root directory of the v-project, the code is as follows:

// @flow

var str = "hello world!";
console.log(str);

Then we run the following command in the root directory of the project, if all goes well, it will prompt the following information:

$ flow check  
Found 0 errors

But if we change the code as shown below; it would be incorrect report, index.js code is changed as follows:

// @flow

var str != "hello world!";
console.log(str);

Execution results as shown below:

Note that the first line, we added // @flow, is used to tell Flow, you need to check my file. If you do not add this comment, Flow will not examine the documents.
Of course, we can force Flow to detect all the files, regardless of file has no @flow comments, we only need to bring --all parameter in the command line on the line, as follows:

$ flow check --all

But this command, we generally still need caution when we are in a large-scale project, if the project introduces a number of third-party libraries, the detector may find a lot of mistakes we do not want.

Note: flow check this order, although it is feasible, but not the most efficient use of the command will flow again each time to check all the files in the project.

4. appreciated Type Comment

Javascript is a weakly typed language, there is no provision in the grammar clear that types, such as JS code to run is normal. 

function add(num1, num2) {
  return num1 + num2;
}

var result = add(1, '2');
console.log(result); // 输出:12

Code above, result in the output value of '12'; however, this is not possible we want, we may want to add two numbers together results obtained, but the preparation of the code, the parameters accidentally written string to go. The expected result is not the same.

Flow through the static type analysis and comment, to help us solve similar problems, so that our code is more in line with expectations.

Type Comment generally to: the beginning, can be used in the method parameters, return value and variable declarations, such as the above code changes using Type Notes:

// @flow

function add(num1:number, num2:number) :number {
  return num1 + num2;
}

var result = add(1, '2'); 

After Run results are shown below:

$ 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

Code above, num1: number and num2: number of means: num1 and num2 parameters are transmitted to a digital type,: number {} in: Meaning number is: returns results are numeric type. If we take the above '2' into the number 2 to normal.

Type of annotation is useful in large, complex project file, it can ensure the code is as expected.

Let us look at Flow can support many more types of annotations, are as follows:

function

// @flow

function add(num1:number, num2:number) :number {
  return num1 + num2;
}

add(1, 2); 

Array

// @flow

var foo : Array<number> = [1, 2, 3];

As the array type annotation format Array <number>, the meaning of each number represents a data type array are number (numeric) type.

class

The following are comments model classes and objects, we can use two types or before (|) logic, variable foo must add type annotations Foo class.

// @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│ */
      67│ 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.

 12│ // @flow
 34│ import _ from 'lodash'; 
 567│

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 [email protected];

文件下载完成后,会自动在我们的项目根目录下 新建一个 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│ ];
  89│ function test() {
 10│   return _.findWhere(obj, {flag: true});
 11│ }
 1213│

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一些基本的知识了解下即可。

Guess you like

Origin www.cnblogs.com/tugenhua0707/p/11760712.html