Flow:Facebook 的 JavaScript 静态类型检查器

Flow:Facebook 的 JavaScript 静态类型检查器

Flow是JavaScript代码的静态类型检查器。 它可以帮助您提高工作效率。 让您的代码更快,更智能,更自信,更大规模。

Flow通过静态类型注释检查代码是否存在错误。 这些类型允许您告诉Flow您希望代码如何工作,Flow将确保它以这种方式工作。

github地址:flow

flow文档地址:flow


1.从demo开始认识flow

2.安装,配置

3.flow总结及使用


1.从demo开始认识flow

1.1 出入参静态类型注释

1
2
3
4
5
6
// @flow
function square(n: number): number {
return n * n;
}

square("2"); // Error!

报错信息:

Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ common/globFile.js:26:8

Cannot call square with ‘2’ bound to n because string [1] is incompatible with number [2].

1.2.运算结果类型检查

因为Flow很好地理解JavaScript,所以它不需要很多这些类型。 你应该只需要做很少的工作来描述你的Flow代码,它将推断其余部分。 在很多时候,Flow可以完全理解您的代码而不需要任何类型

1
2
3
4
5
6
// @flow
function square(n) {
return n * n; // Error!
}

square("2");

报错信息:
Cannot perform arithmetic operation because string [1] is not a number.

2.安装

2.1 安装编译器

官方推荐babel或flow-remove-types

1
npm install --save-dev @babel/cli @babel/preset-flow

项目增加babel.config.js文件

1
2
3
4
5
6
7
module.exports = function() {
return {
presets: [
"@babel/preset-flow"
]
}
}

package.json中添加scripts

1
2
3
4
5
6
7
8
9
10
{
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/preset-flow": "^7.0.0",
},
"scripts": {
"build": "babel src/ -d lib/",
"prepublish": "npm run build"
}
}

2.2 安装flow

1
npm install --save-dev flow-bin

package.json中添加scripts

1
2
3
4
5
6
7
8
{
"devDependencies": {
"flow-bin": "^0.99.0"
},
"scripts": {
"flow": "flow"
}
}

生成flowconfig配置文件

1
npm run flow init

运行flow

1
npm run flow

3.flow总结及使用

3.1 使用flow init 初始化项目

生成类似INI格式,项目.flowconfig配置文件

3.1.1 .flowconfig由6个部分组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
; 忽略匹配文件
[ignore]
<PROJECT_ROOT>/__tests__/.*
<PROJECT_ROOT>/lib/.*

; 包含指定的文件或目录
[include]
<PROJECT_ROOT>/src/.*

; 在类型检查代码时包含指定的库定义
[libs]

; lint
[lints]
all=warn
untyped-type-import=error
sketchy-null-bool=off

; 选项
[options]
all=true
esproposal.decorators=ignore
experimental.const_params=true
module.file_ext=.bar
module.use_strict=true

; 严格
[strict]
nonstrict-import
unclear-type
unsafe-getters-setters
untyped-import
untyped-type-import


; none
; 在声明模式下,代码没有进行类型检查,会检查文件内容
[declarations]
<PROJECT_ROOT>/third_party/.*

; 不检查文件内容,不匹配指定正则表达式的类型文件,丢弃类型并将模块视为任何模块
[untyped]
<PROJECT_ROOT>/third_party/.*

; 指定flow使用的版本
[version]
0.98.1

3.1.2 # or ; or 💩 are ignored

1
2
3
4
5
6
# This is a comment
# This is a comment
; This is a comment
; This is a comment
💩 This is a comment
💩 This is a comment

3.1.3 .flowconfig放置位置

.flowconfig的位置非常重要。Flow将包含.flowconfig的目录视为项目根目录。 默认情况下,Flow包含项目根目录下的所有源代码

3.2 使用flow启动flow后台进程

vscode推荐安装Flow Language Support

1
2
flow status // 启动flow后台进程
flow stop // 终止flow后台进程

webpack热加载,使用flow-webpack-plugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'use strict';

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const FlowWebpackPlugin = require('flow-webpack-plugin');

module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './example/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist'),
},
devServer: {
hot: true,
disableHostCheck: true,
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({ template: 'example/index.html' }),
new FlowWebpackPlugin({
flowArgs: ['check']
})
],
};

3.3 使用// @flow确定Flow将监视哪些文件

Flow后台进程使用此标志收集所有文件,并使用所有这些文件中提供的类型信息来确保一致性和无错误编程

使用JavaScript注释的形式,注释@flow

1
2
3
4
5
// @flow



/* @flow */

忽略//@flow,检查所有文件

1
flow check --all

3.4 编写flow代码

Flow后台进程将会捕获此错误

1
2
3
4
5
6
7
8
// @flow

function foo(x: ?number): string {
if (x) {
return x;
}
return "default string";
}

3.5 检查代码是否存在类型错误

1
2
# equivalent to `flow status`
flow

运行flow检查

1
2
3
4
5
6
7
8
// @flow

function foo(x: ?number): string {
if (x) {
return x; // Cannot return `x` because number [1] is incompatible with string [2].
}
return "default string";
}

3.6 如何在代码中添加类型注释

类型注释符号

1
2
3
|       // 或
& // 且
? // 可选

类型注释中包括的类型

1
2
3
4
5
6
7
8
9
10
boolean                                 // true or new Boolean(false)
string // "hello" or new String("world")
number // 3.14 or new Number(42)
null // null
undefined (void in Flow types) // undefined
Array (其中T用来描述数组中值的类型) // Array<T>
Object // {}
Function // function
class // class
Symbols (not yet supported in Flow) // Symbol("foo")

小写

1
2
3
4
5
6
// @flow
function method(x: number, y: string, z: boolean) {
// ...
}

method(3.14, "hello", true);

大写

1
2
3
4
5
6
// @flow
function method(x: Number, y: String, z: Boolean) {
// ...
}

method(new Number(42), new String("world"), new Boolean(false));

boolean

1
2
3
4
5
6
7
8
// @flow
function acceptsBoolean(value: boolean) {
// ...
}

acceptsBoolean(true); // Works!
acceptsBoolean(false); // Works!
acceptsBoolean("foo"); // Error!

JavaScript可以隐式地将其他类型的值转换为布尔值

1
2
if (42) {} // 42 => true
if ("") {} // "" => false

非布尔值需要显式转换为布尔类型

1
2
3
4
5
6
7
8
// @flow
function acceptsBoolean(value: boolean) {
// ...
}

acceptsBoolean(0); // Error!
acceptsBoolean(Boolean(0)); // Works!
acceptsBoolean(!!0); // Works!

string

1
2
3
4
5
6
7
// @flow
function acceptsString(value: string) {
// ...
}

acceptsString("foo"); // Works!
acceptsString(false); // Error!

JavaScript可以隐式地将其他类型的值转换为字符

1
2
"foo" + 42; // "foo42"
"foo" + {}; // "foo[object Object]"

Flow连接到字符串时只接受字符串和数字。

1
2
3
4
5
// @flow
"foo" + "foo"; // Works!
"foo" + 42; // Works!
"foo" + {}; // Error!
"foo" + []; // Error!

必须明确并将其他类型转换为字符串

1
2
3
4
// @flow
"foo" + String({}); // Works!
"foo" + [].toString(); // Works!
"" + JSON.stringify({}) // Works!

number

1
2
3
4
5
6
7
8
9
10
// @flow
function acceptsNumber(value: number) {
// ...
}

acceptsNumber(42); // Works!
acceptsNumber(3.14); // Works!
acceptsNumber(NaN); // Works!
acceptsNumber(Infinity); // Works!
acceptsNumber("foo"); // Error!

null and void

1
2
3
4
5
6
7
8
9
10
11
12
13
// @flow
function acceptsNull(value: null) {
/* ... */
}

function acceptsUndefined(value: void) {
/* ... */
}

acceptsNull(null); // Works!
acceptsNull(undefined); // Error!
acceptsUndefined(null); // Error!
acceptsUndefined(undefined); // Works!

Array

1
2
3
4
let arr: Array<number> = [1, 2, 3];
let arr1: Array<boolean> = [true, false, true];
let arr2: Array<string> = ["A", "B", "C"];
let arr3: Array<mixed> = [1, true, "three"]

Object

1
2
3
4
5
6
7
8
9
10
11
// @flow
var obj1: { foo: boolean } = { foo: true };
var obj2: {
foo: number,
bar: boolean,
baz: string,
} = {
foo: 1,
bar: true,
baz: 'three',
};

Function

1
2
3
4
5
6
7
8
// @flow
function concat(a: string, b: string): string {
return a + b;
}

concat("foo", "bar"); // Works!
// $ExpectError
concat(true, false); // Error!

箭头Function

1
2
3
4
5
6
7
let method = (str, bool, ...nums) => {
// ...
};

let method = (str: string, bool?: boolean, ...nums: Array<number>): void => {
// ...
};

回调Function

1
2
3
function method(callback: (error: Error | null, value: string | null) => void) {
// ...
}

class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// @flow
class MyClass<A, B, C> {
constructor(arg1: A, arg2: B, arg3: C) {
// ...
}
}

var val: MyClass<number, boolean, string> = new MyClass(1, true, 'three');


class Foo {
serialize() { return '[Foo]'; }
}

class Bar {
serialize() { return '[Bar]'; }
}

// $ExpectError
const foo: Foo = new Bar(); // Error!

Maybe Types

1
2
3
4
5
6
7
8
9
10
11
12
// @flow
function acceptsMaybeString(value: ?string) {
// ...
}

acceptsMaybeString("bar"); // Works!
acceptsMaybeString(undefined); // Works!
acceptsMaybeString(null); // Works!
acceptsMaybeString(); // Works!
acceptsMaybeString(12345); // Error!

// value: string null or undefined

对象属性可选

1
2
3
4
5
6
7
8
9
// @flow
function acceptsObject(value: { foo?: string }) {
// ...
}

acceptsObject({ foo: "bar" }); // Works!
acceptsObject({ foo: undefined }); // Works!
acceptsObject({ foo: null }); // Error!问号放在string前不报错
acceptsObject({}); // Works!

函数参数可选

1
2
3
4
5
6
7
8
9
// @flow
function acceptsOptionalString(value?: string) {
// ...
}

acceptsOptionalString("bar"); // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null); // Error!问号放在string前不报错
acceptsOptionalString(); // Works!

带默认值的函数参数

1
2
3
4
5
6
7
8
9
// @flow
function acceptsOptionalString(value: string = "foo") {
// ...
}

acceptsOptionalString("bar"); // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null); // Error!
acceptsOptionalString(); // Works!

使用字面文字作为类型

1
2
3
4
5
6
7
8
9
10
// @flow
function acceptsTwo(value: 2) {
// ...
}

acceptsTwo(2); // Works!
// $ExpectError
acceptsTwo(3); // Error!
// $ExpectError
acceptsTwo("2"); // Error!

Union Types

1
2
3
4
5
6
7
8
9
10
11
12
13
// @flow
function getColor(name: "success" | "warning" | "danger") {
switch (name) {
case "success" : return "green";
case "warning" : return "yellow";
case "danger" : return "red";
}
}

getColor("success"); // Works!
getColor("danger"); // Works!
// $ExpectError
getColor("error"); // Error!

Mixed Types

1
2
3
function stringifyBasicValue(value: string | number) {
return '' + value;
}

A type based on another type

1
2
3
function identity<T>(value: T): T {
return value;
}

An arbitrary type that could be anything

1
2
3
function getTypeOf(value: mixed): string {
return typeof value;
}

Any Types

1
2
3
4
5
6
7
8
// @flow
function add(one: any, two: any): number {
return one + two;
}

add(1, 2); // Works.
add("1", "2"); // Works.
add({}, []); // Works.

变量类型
将类型添加到变量声明
const let var

1
2
3
4
5
6
7
8
// @flow
const foo /* : number */ = 1;
const bar: number = 2;

var fooVar /* : number */ = 1;
let fooLet /* : number */ = 1;
var barVar: number = 2;
let barLet: number = 2;

let

1
2
3
4
let foo: number = 1;
foo = 2; // Works!
// $ExpectError
foo = "3"; // Error!

重新分配变量

1
2
3
4
5
6
let foo = 42;

if (Math.random()) foo = true;
if (Math.random()) foo = "hello";

let isOneOf: number | boolean | string = foo; // Works!

重新分配变量确定变量类型

1
2
3
4
5
6
7
8
9
// @flow
let foo = 42;
let isNumber: number = foo; // Works!

foo = true;
let isBoolean: boolean = foo; // Works!

foo = "hello";
let isString: string = foo; // Works!

react

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import * as React from 'react';

type Props = {
foo: number,
bar?: string,
};

type State = {
count: number,
};


class MyComponent extends React.Component<Props> {
state = {
count: 0,
};
render() {
this.props.doesNotExist; // Error! You did not define a `doesNotExist` prop.

return <div>{this.props.bar}</div>;
}
}

<MyComponent foo={42} />;

0%