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

35次阅读

共计 7511 个字符,预计需要花费 19 分钟才能阅读完成。

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

一、Flow 是什么

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

// @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 设置编译器
yarn add --dev babel-cli babel-preset-flow

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

{"presets": ["flow"]
}

其中 "flow" 就是指刚才安装的 babel-preset-flow 的简写,省略了 babel-preset-
也可以将这个命令配置到 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 安装到当前项目目录,而不是全局安装

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

# 允许在 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 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.3 类型自动检查

使用 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

正文完
 0