2

Flow <JavaScript静态类型检测工具>

一、Flow 是什么

Flow是一个由Facebook出品的JavaScript静态类型检查工具,它与Typescript不同的是,它可以部分引入,不需要完全重构整个项目,所以对于一个已有一定规模的项目来说,迁移成本更小,也更加可行。除此之外,Flow可以提供实时增量的反馈,通过运行Flow server不需要在每次更改项目的时候完全从头运行类型检查,提高运行效率。目前React和Vue均采用了Flow作为静态检查的工具

// @flow
function square(n: number): number {
  return n * n;
}

square("2", "2"); // Error!

使用flow之后,square函数的参数和返回值,都可以指定类型。当你在代码中写square('2', '2')的时候,用flow命令一检查,不需要看业务逻辑,就知道调用的参数有错误

1.1 Flow的作用

Flow可以帮助找出由于不合理的类型操作引起的错误,包括运算符操作,函数参数类型和返回值类型等。Flow也支持自定义类型声明,泛型声明等类型语言相关的操作。

1.2 Flow的优点

所谓类型检查,就是在编译期尽早发现(由类型错误引起的)bug,又不影响代码运行(不需要运行时动态检查类型),使编写js具有和编写Java等强类型语言相近的体验。
1、使得大型项目可维护
2、增加代码的可读性
3、通常会有更好的IDE支持
4、几乎消灭了由函数数据类型引起的bug
5、无需额外的关于变量、参数、返回值类型的注释,可以让读者了解必要的附加信息
6、大量减少由于使用第三方库不当引起的类型错误
7、可以在CI系统中集成
8、工具链配置成本比较低,只需要很少的工作量即可达到这些效果
9、

1.3 Flow 与 TypeScript 的区别

工具 Flow TypeScript
出品公司 facebook 微软
star 20.7k+ 61.2k+
文档支持程度 一般 广泛
第三方库支持工具 Flow-typed tsd
IDE支持 Webstorm自带插件支持 Webstorm支持,Visual Studio原生支持
其他 自由度更高,老项目的迁移成本低 工程化强,社区活跃度和官方支持力度更高,适合新项目

两者在代码语法上有大量相似的地方,其中对于一些数据类型的支持不一样,具体请查看Flow的文档。关于Flow和Typescript的比较,可以简单总结为:对于新项目,可以考虑使用TypeScript或者Flow,对于已有一定规模的项目则建议使用Flow进行较小成本的逐步迁移来引入类型检查。

1.4 Flow如何进行类型检查

Flow 的类型检查方式

1.类型推断:通过变量的使用上下文来推断出变量类型,然后根据这些推断来检查类型。
2.类型注释:事先注释好我们期待的类型,Flow 会基于这些注释来判断。
类型推断

function split(str) {
    return str.split('')
}
split(11)

Flow 检查上述代码后会报错,因为函数 split 期待的参数是字符串,而我们输入了数字。
类型注释
添加类型注释可以提供更好更明确的检查依据

/*@flow*/
function test(x: number, y: number): number {
    return x + y
}
test('str', 0)

因为函数参数的期待类型为数字,而我们提供了字符串。flow会报错

Flow 的工作流程

第一步:模仿 C#/Java 之类的语言,在编码过程中对类型进行定义。
下面demo根据 Flow 的规则,进行类型声明的代码。虽然看起来没有什么用,但可以简单讲述如何定义类型了。这个函数接收一个 string 类型的参数,参数名为 str,函数的返回值是 number 类型。定义了一个类型为 number 的常量 len,它的值为 str 的长度,并且将其返回。

function getStringLength(str: string):number {
    const len: number = str.length;
    return len;
}

第二步:通过 Flow 提供的工具进行类型检查。如果有类型不匹配的地方,工具会提示错误。
第三步:发布到线上的代码是不能加类型声明的,因为这样不符合规范,浏览器也不认。所以,我们需要对源代码进行一次编译,将所有声明的类型去掉,像正常的 JS 代码一样了。上面的代码编译过后将会变成:

function getStringLength(str) {
    const len = str.length;
    return len;
}

二、基础类型检测

flow.js 中定义了的5种最简单的类型,(都是小写),其中void对应js中的undefined
要想加入到javascript中,只需要在关键的地方声明想要的类型。其它时间我们的代码还是熟悉的javascript

2.1 boolean

//flow/src/demo.js
//在文件的头部加入,用注释加入 `@flow` 声明,这样flow.js才会检查这个文件。
//@flow
//在声明变量时,在变量名加入 `:[Type]` 来表明变量的类型,其它类型同理。
var bol:boolean = true;
//当然,也可以不加类型,这样就跟原来的js一样了。
var bol = true;

2.2 number

//flow/src/demo.js

//在文件的头部加入,用注释加入 `@flow` 声明,这样flow.js才会检查这个文件。
//@flow
//在声明变量时,在变量名加入 `:[Type]` 来表明变量的类型,其它类型同理。
var num:number = 1;
//当然,也可以不加类型,这样就跟原来的js一样了。
var num = 1;

2.3 string

//flow/src/demo.js

//在文件的头部加入,用注释加入 `@flow` 声明,这样flow.js才会检查这个文件。
//@flow
//在声明变量时,在变量名加入 `:[Type]` 来表明变量的类型,其它类型同理。
var str:string = 'xiaofang';
//当然,也可以不加类型,这样就跟原来的js一样了。
var str = 'xiaofang';

2.4 null

//flow/src/demo.js

//在文件的头部加入,用注释加入 `@flow` 声明,这样flow.js才会检查这个文件。
//@flow
//在声明变量时,在变量名加入 `:[Type]` 来表明变量的类型,其它类型同理。
var aa:null = null;
//当然,也可以不加类型,这样就跟原来的js一样了。
var aa = null;

如果先让任意类型可以是 null 或者 undefined 则需要写成 ?T 的格式即可,注意T就是类型

/*@flow*/
var age: ?number = null
age可以为数字或者 null

2.5 void

//flow/src/demo.js

//在文件的头部加入,用注释加入 `@flow` 声明,这样flow.js才会检查这个文件。
//@flow
//在声明变量时,在变量名加入 `:[Type]` 来表明变量的类型,其它类型同理。
var vv:void = void;
//当然,也可以不加类型,这样就跟原来的js一样了。
var vv = void;

三、复杂类型检测

以下几个类型比较复杂,而且可以相互嵌套。

3.1 对象:Object

//flow/src/demo.js
//@flow

//Object大写的O
var o:Object = {
  hello:'h'
};

//声明了Object的key
var o2:{key:string} = {
  key:'z233'
};

3.2 数组:Array

//flow/src/demo.js
//@flow

//基于基本类似的数组,数组内都是相同类型
var numberArr:number[] = [12,3,4,5,2];
//另一个写法
var numberAr2r:Array<number> = [12,3,2,3];

var stringArr:string[] = ['12','a','cc'];
var booleanArr:boolean[] = [true,true,false];
var nullArr:null[] = [null,null,null];
var voidArr:void[] = [ , , undefined,void(0)];

//数组内包含各个不同的类型数据
//第4个原素没有声明,则可以是任意类型
var arr:[number,string,boolean] = [1,'a',true,function(){},];

3.3 函数

函数比较特殊,因为函数的核心在于参数和返回值,函数作为类型本身并没有作用。

//flow/src/demo.js
//@flow

/**
 * 声明带类型的函数
 * 这里是声明一个函数fn,规定了自己需要的参数类型和返回值类型。
 */
function fn(arg:number,arg2:string):Object{
  return {
    arg,
    arg2
  }
}
//同理,ES2015箭头函数的写法
var fn2 = (arg:number,arg2:string):Object => {
  return {
    arg,
    arg2
  }
}

/**
 * 这里是声明变量fn2,规定了它所需的函数的特征:
 * 参数: (arg:string,arg2:number)
 * 返回值:Object
 */
var fn3:(arg:string,arg2:number)=>Object = function(){
  return {}
}

/**
 * 对比下面这种写法,
 * 两者的声明的地方不一样,造成的意义也不同。
 */
var fn4 = function(arg:string,arg2:Object):number{
  return 1;
}

3.4 自定义Class

声明一个自定义类,然后用法如同基本类型

//flow/src/demo.js

/*@flow*/

class Person {

constructor(name: string, age: string | number) {
this.name= name
this.age= age
this.sex= false
}}

var per: Person = new Person('xiaoli', 4)

var obj: { arr: Array<string>, per: Person} = {
arr: ['hello']
per: new Person('hello', 3)
}

四、如何使用

4.1 配置过程

4.1.1 设置编译器

babel低版本

yarn add --dev babel-cli babel-preset-flow

babel高版本

yarn add --dev @babel/cli @babel/preset-flow

在根目录新建一个.babelrc文件,并配置flow作为presets
babel低版本配置.babelrc文件

{
  "presets": ["flow"]
}

其中"flow"就是指刚才安装的babel-preset-flow的简写,省略了babel-preset-
babel高版本配置.babelrc文件

{
  "presets": ["@babel/preset-flow"]
}

也可以将这个命令配置到package.json文件中:

{
  "name": "flow-demo",
  "main": "lib/index.js",
  "scripts": {
    "build": "babel src/ -D lib/",
    "prepublish": "yarn run build"
  }
}

通过以上配置babel,经过编译去掉了类型约束
去类型前

// @flow
function square(n:number): number {
    return n * n;
}

square(2)

编译去类型之后

function square(n) {
    return n * n;
}

square(2);

去flow类型注解的另一种方法 flow-remove-types
官方文档:https://flow.org/en/docs/tool...

# 如果没有package.json文件,先生成
yarn add --dev flow-remove-types
# or
npm install --save-dev flow-remove-types

去类型

# 为了方便,先将a.js移到src目录下
$ yarn run flow-remove-types src/ -d dist/
yarn run v1.12.3
$ F:\youshengyouse\del\node_modules\.bin\flow-remove-types src/ -d dist/
src\a.js
 ↳ dist\a.js
Done in 0.30s.
4.1.2 设置flow

推荐将flow安装到当前项目目录,而不是全局安装,flow-bin是Flow的二进制包装器—— JavaScript 的静态类型检查器

yarn add --dev flow-bin

运行yarn run flow init生成配置文件.flowconfig
运行yarn run flow进行代码检查
停用flow后台进程,使用flow stop
3.flow配置文件.flowconfig
flowconfig大概遵循INI文档格式。一个.flowconfig文件,可以包含下以五个部分:

[include]
[ignore] 用正则表达式匹配文件或目录,满足条件的将被flow忽略;<PROJECT_ROOT>表示项目根目录
[libs]
[options]
[version]

[ignore]

[ignore]
# Ignore Docs
<PROJECT_ROOT>/docs/.*
<PROJECT_ROOT>/.*/docs/.*

react的ignore部分,都使用了<PROJECT_ROOT>这种绝对路径的写法。在配置中使用注释,可以在行首加#

[ignore]
<PROJECT_ROOT>/.*/node_modules/y18n/.*

[libs]

[libs]
./node_modules/fbjs/flow/lib/dev.js
./flow

[options]

[options]
# 可选项node|haste,haste已不再被维护,可react还在用
module.system=haste

module.file_ext=.vue 
module.file_ext=.js

# 允许在class中使用static关键字
esproposal.class_static_fields=enable
# 允许在class中使用instance关键字
esproposal.class_instance_fields=enable

# 不允许在class中使用下划线作为私有函数
munge_underscores=false

# 约束的类型,可以用来代替TODO
suppress_type=$FlowFixMe
# 这个正则是什么含义?TODO
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-3]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*www[a-z,_]*\\)?)\\)

[version]

[version]
^0.41.0

version是指flow-bin的版本,可以用yarn run flow version查看

4.2 JS文件中使用

// @flow
function square(n: number): number {
  return n * n;
}

square("2", "2"); // Error!

4.3 VUE组件中使用

方法一

注释掉template, style和script标签,由于Vue的编译器即使注释了也会识别其中的<template>, <style> 和 <script> 标签,而Flow检查会忽略注释,因此对于Flow来说可以当做一个javascript文件进行处理
对于这样处理的vue文件,Flow命令能够报出关于一般的函数声明的类型检查错误,但是对于绑定到Vue实例(this)上的方法是无效的。因此Flow类型检查不是100%覆盖。这种方法的主要问题在于代码和注释混用不便于阅读

// @flow
/*
<style>
</style>
<template>
  <div>
     <p>这是一个栗子</p>
  </div>
</template>
*/
//<script>
    function square(n:number): number {
        return n * n;
    }
//</script>
方法二

Vue文件引用外部的js文件,将js部分单独抽离出来进行类型检查。该方法的优点在于可以用到Flow的所有功能,但是没有了vue单文件组件的结构,项目结构略显臃肿。(每个组件都会有至少两个文件)

<template>
  <div>
     <p>这是一个栗子</p>
  </div>
</template>
<script src="./flow-vue.js">
</script>

4.4 在webpack编译中使用

安装一个 flow 的 webpack 插件
npm install flow-webpack-plugin --save-dev 

配置到 webpack

// 默认情况下,是调用的 flow status。但是我在vue中使用,报错有点看不懂。
// 所以我是配置为使用 flow check
var FlowWebpackPlugin = require('flow-webpack-plugin');
plugins: [
    new FlowWebpackPlugin({
      flowArgs: ['check']
    })
]

最后,运行 npm run dev 就可以边改边看到 flow 的类型校验信息了

4.5 其他常用依赖解释(以babel高版本插件为例?):

npm install @babel/plugin-transform-flow-strip-types --save-dev
npm install @babel/plugin-syntax-flow --save-dev
npm install @babel/plugin-proposal-class-properties --save-dev
npm install @babel/plugin-transform-flow-comments --save-dev
npm install eslint-plugin-flowtype-errors --save-dev

其中前面安装的@babel/preset-flow包含@babel/plugin-transform-flow-strip-types,编译的时候去除类型注释的作用

插件在.babelrc中配置方式

然后在.babelrc中引入这几个插件即可:

"plugins": [
    ...
    ["@babel/plugin-proposal-class-properties", { "loose": true }], //对类属性和静态方法的支持
    "@babel/plugin-syntax-flow", //对 Flow 语法的支持
    "@babel/plugin-transform-flow-comments", //使用 Babel 进行编译之前,从源文件中将 Flow 语法指令转换为注释代码
    "@babel/plugin-transform-flow-strip-types" //去除类型注释插件
    ...
]
插件在.eslintrc中配置方式
"plugins": ["flowtype-errors" //将 Flow 错误通过 ESlint 传递给编辑器的 eslint 插件(如果有的话],
 "rules": {
       "flowtype-errors/show-errors": 2 
  }  

注意插件顺序。这与 Babel 预设和插件运行顺序有关。
Babel 预设与插件运行顺序:

  • 插件在预设之前运行。
  • 插件从第一至最后顺序运行。
  • 预设顺序相反(从最后到第一)。

例如:

"plugins": [
    "transform-decorators-legacy",
    ["@babel/plugin-proposal-class-properties", { "loose": true }]
]

先运行 transform-decorators-legacy 然后再运行
@babel/plugin-proposal-class-properties
重要的是要记住,对于预设,顺序是相反的。下列:

{
  "presets": ["es2015", "react", "stage-2"]
}

将按照以下顺序运行:stage-2,react,然后 es2015。

五、类型自动检查

使用flow check来进行类型检查,不是很方便,我想babel的插件来进行类型检查,并在编辑器如vs code中自动检查,这样效率就会高很多,这就要用到插件babel-plugin-typecheck,它与预置flow的功能完全不一样,babel-plugin-typecheck是检查代码中的类型是否有错,babel-preset-flow是负责删除类型的,这样js代码在执行时就好象从来没有加过类型一样。

在vs code中配置类型

VS Code中搜索flow,发现有vscode-flow-ide、Flow-Language-Support两个插件,在这里以vscode-flow-ide为例
先安装vscode-flow-ide
条件:

  1. 项目根目录有配置文件.flowconfig
  2. nodejs添加到了环境变量path中
  3. 全局或项目内安装了flow,推荐全局安装flow-bin

配置(默认就行)
VS Code左下角管理/设置/User Settings/Extensions/Flow-IDE Configurations(只有启用后才能配置,否则找不到这项),下面是文字版,实际上在面板中就可以设置

  • flowide.enable: 启用/关闭
  • flowide.pathToFlow: Absolute path to the Flow executable. Set it only if the default behaviour of the extension doesn't work out for you. The extension will try first to read it from local node_modules/flow-bin or globally if not otherwise set here.
  • flowide.useCodeSnippetsOnFunctionSuggest - Add the function paramters when selecting a function to autocomple.

重启vs Code,就会发现可以报错了,现在可以去掉顶部的// @flow及传递不合要求的参数测试下。

如果在problem窗口有错误,[ts]'types' can only be used in a .ts file. 8010,请在扩展中找到typescript,找到"javascript.validate.enable": false

这两个VSCode的插件,看介绍能够直接对JS文件进行Flow方面的Lint工作,能够发现Flow方面的问题。但是使用中发现无法对.vue文件进行这种工作


透明技术人
200 声望3 粉丝