Flow
一、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 |
---|---|---|
出品公司 | 微软 | |
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 = [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, 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忽略;表示项目根目录
[libs]
[options]
[version]
[ignore]
[ignore]
# Ignore Docs
/docs/.*
/.*/docs/.*
react的ignore部分,都使用了#
号
[ignore]
/.*/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的编译器即使注释了也会识别其中的,
这是一个栗子 Vue文件引用外部的js文件,将js部分单独抽离出来进行类型检查。该方法的优点在于可以用到Flow的所有功能,但是没有了vue单文件组件的结构,项目结构略显臃肿。(每个组件都会有至少两个文件) 这是一个栗子 配置到 webpack 最后,运行 npm run dev 就可以边改边看到 flow 的类型校验信息了 其中前面安装的 然后在.babelrc中引入这几个插件即可: 注意插件顺序。这与 Babel 预设和插件运行顺序有关。 例如: 先运行 transform-decorators-legacy 然后再运行 将按照以下顺序运行:stage-2,react,然后 es2015。 使用flow check来进行类型检查,不是很方便,我想babel的插件来进行类型检查,并在编辑器如 在 配置(默认就行) 重启vs Code,就会发现可以报错了,现在可以去掉顶部的 如果在problem窗口有错误, 这两个VSCode的插件,看介绍能够直接对JS文件进行Flow方面的Lint工作,能够发现Flow方面的问题。但是使用中发现无法对.vue文件进行这种工作方法二
4.4 在webpack编译中使用
安装一个 flow 的 webpack 插件
npm install flow-webpack-plugin --save-dev
// 默认情况下,是调用的 flow status。但是我在vue中使用,报错有点看不懂。
// 所以我是配置为使用 flow check
var FlowWebpackPlugin = require('flow-webpack-plugin');
plugins: [
new FlowWebpackPlugin({
flowArgs: ['check']
})
]
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中配置方式
"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 预设与插件运行顺序:
"plugins": [
"transform-decorators-legacy",
["@babel/plugin-proposal-class-properties", { "loose": true }]
]
@babel/plugin-proposal-class-properties
重要的是要记住,对于预设,顺序是相反的。下列:{
"presets": ["es2015", "react", "stage-2"]
}
五、类型自动检查
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
条件:
.flowconfig
flow-bin
VS Code
左下角管理/设置/User Settings/Extensions/Flow-IDE Configurations(只有启用后才能配置,否则找不到这项),下面是文字版,实际上在面板中就可以设置
// @flow
及传递不合要求的参数测试下。[ts]'types' can only be used in a .ts file. 8010
,请在扩展中找到typescript,找到"javascript.validate.enable": false