Flow知识图谱及其在前端项目中的应用

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
出品公司 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 = [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的编译器即使注释了也会识别其中的