flow 使用详解 + 小结
Posted GoldenaArcher
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flow 使用详解 + 小结相关的知识,希望对你有一定的参考价值。
flow 使用详解 + 小结
出现 flow 这种静态检查的工具是与 javascript 的使用有些关系的——这是一种弱类型同事是动态类型的语言。这就造成了 JavaScript 会有一些比较哪恼人的特性,如可以不声明直接使用,又比如在运行时动态的决定变量的类型,如:
// 未声明就是用
a = 10; // 可以运行,不会有问题
a = 'hello world'; // 修改了 a 的类型,但是也没有问题,不会报错
这种特性在个人项目中可能不是问题,还是写起来会很方便的一个特点。但是一旦项目体量比较大了,那么实现起来就会有不便之处。
以个人亲身经历来说,第一个写的 React 项目是将原本的 .Net 项目用 React 进行重构,所以参考的对象有源码与后端 API 传来的数据。这个时候就有一个实践上的问题了,后端传来的 JSON 数据中的 id 是数字类型,而在源码中的 Model 里是字符串的类型。
抛开沟通上的问题不说,在具体的实践过程中就会出现两个不同的选择,直接将数据库中的数据放入 session storage 和 redux 中,那么数据类型就是数字,但是将数据放到 Model 之后,数据的格式又变成了字符串。并且在开发过程中,又偶尔会碰到更新 id 的情况,这也就导致了几乎无法相信存储的数据格式,不断的使用 parseInt()
对两组 id 进行比较。
使用 flow 这种静态检查类的工具可以有效的避免这个问题,并且可以通过类型注解对所传递的数据进行规定和检查。这样,也就能够避免开发到了一半,突然发现因为数据类型格式不同而导致可能出现的各种问题。
而且 flow 的优点在于,它是一个小型工具,所以使用简单。
flow 的安装和配置
这里依旧会使用 npm/yarn 新建一个项目,然后将 flow 保存到本地的 package.json 中去。
设置编译器
babel 编译器是官方推荐的,移除注解的方式。毕竟 flow 的语法在其他的 JavaScript 运行时无法运行。
flow> yarn add --dev @babel/core @babel/cli @babel/preset-flow
这里依旧是请出万能的 babel 来进行配置,首先在根目录下新建一个 .babelrc
文件,内容如下:
{
"presets": ["@babel/preset-flow"]
}
这样,就可以通过运行下面的命令将 src 目录中的文件编译并放到 lib 文件夹中了:
flow> yarn run babel src/ -d lib/
yarn run v1.22.10
warning package.json: No license field
$ C:\\assignment\\front\\flow\\node_modules\\.bin\\babel src/ -d lib/
Successfully compiled 1 file with Babel (395ms).
Done in 0.81s.
运行后的结果:
看到可以成功编译了,就可以将这段复杂的代码丢到 package.json 里面去了:
{
"scripts": {
"build": "babel src/ -d lib/"
}
}
接下来运行 yarn build
就行了。
设置 babel 主要是为了能够编译需要被部署的代码,毕竟 flow 的语法不是标准语法,无法正确运行。运行后的对比结果如下:
可以看到,bin/test.js 的代码就是正常且正确的 JavaScript 代码,而 src/test.js 是会报错(这是由浏览器和我自己安装的插件造成的报错)的代码。
设置 flow
安装和运行都非常简单:
# 添加依赖
flow> yarn add --dev flow-bin
# 初始化配置,不初始化可能会报错
flow> yarn run flow init
# 这里才是运行
flow> yarn run flow
这样项目配置就已经成功了,而想要使用 flow 还有一步最重要的:就是在开头添加注释。
// @flow
const add = (x: number, y: number) => {
return x + y;
};
const res = add('2', '5');
保存上面的代码,再运行一遍 yarn run flow
命令行就会出现报错:
其原因就是因为在 add 中指定的两个参数必须要数字类型,而这里传进去的是字符串,类型不符。
最后,VSCode 会自动开启对 JavaScript 的校验,而 flow 其实不是合法的 JavaScript 语法——这也是为什么需要用 babel 插件去重新编译一遍代码移除 flow 的语法。
如果不想看太多错误信息心烦,可以到设置里面去关闭 JavaScript 的校验:
这里就将这个 workspace 下的校验关闭了,也不会影响到别的 workspace 的工作流。
注:在完成配置的情况下,VSCode 中也可以使用 flow 的官方插件:Flow Language Support 去在代码中更直观的观测到错误,如:
这个插件不是 VSCode 的原生插件,所以体验感上可能会有一定的问题。
flow 的语法
这里会先从熟悉的一些属性开始,列举一些比较常用的功能,或是日常开发比较容易踩坑的点。
这里没有列举所有的功能,例如说省略了 union, tuple, interface, generic 等用的频率相对而言不是很高的功能,建议可以到官方文档继续看看:https://flow.org/en/docs/types/
flow 类型注解
上面的案例使用的就是类型注解,即 type annotations。
类型注解是为了让 flow 知道当前变量应该存储的类型结构,类似于其他语言声明变量时赋予正确的类型:
int num; // int 类型的变量
String str; // String类型的变量
// 函数声明,表达返回值会是一个int,接受的参数是两个int类型
public int sum(int a, int b) {
return a + b;
}
使用 flow 实现的 JavaScript 写法如下:
// @flow
let num: number;
let str: string;
// 注意这里标注 返回值 所在的地方,是在参数的后面
const sum = (a: number, b: number): number => {
return a + b;
};
// 函数式写法
function sum2(a: number, b: number): number {
return a + b;
}
// 不返回任何值时使用void关键字
const noReturn = (): void => {
console.log('void');
};
console.log(sum(10, 20));
如果声明了返回值,又不提供一个返回值,那么 flow 就会默认这个语法是有问题的,并且会报错:
flow 原始类型
根据文档上来说,flow 支持所有 ES6 之前的原始数据类型:
-
Booleans
boolean 只能存储
true
和false
const t: boolean = true; const f: boolean = false;
-
Strings
string 类型能够存储字符串
const str: string = 'string'; // 特别注意字符串拼接这块,flow只允许 字符串 + 字符串/数字 的拼接 // 刚开始习惯了隐式转换可能会注意不到这点 console.log('str' + 'str'); console.log('str' + 666); console.log('str' + false); // cannot 警告
-
Numbers
number 类型可以存储数字,包括两个特殊值
const num: number = 10; const nan: number = NaN; const inf: number = Infinity;
-
null
特殊值,只能保存 null
const n: null = null;
-
undefined
(void
in Flow types)也是特殊值,表示未定义时就用 void 存储
const undef: void = undefined;
-
Symbols (new in ECMAScript 2015)
ES6 新出的特殊类型,用 symbol 表示
const sym: symbol = Symbol();
需要注意的是,原始类型同样包括原始值包装类型,并且使用原始值包装类型的声明会有些不太一样,下面 method 的代码来自官方教程:
// 包装类型
// 非原始包装类型 vs 原始值 最大的差别就在于声明上的差异了
function method(x: Number, y: String, z: Boolean) {
console.log('hello world');
}
method(new Number(42), new String('world'), new Boolean(false));
method(20, 'hello', false);
需要注意的是,在这种情况下,flow 认为原始类型与原始包装类型状态不匹配,并且弹出报错。
flow 数组类型
数组是日常开发中的使用之一,这里简单的讲一下数组类型是怎么使用、声明和规范的。
数组类型的声明
在 flow 中,它有两种声明方式:
-
使用
Array<Type>
语法const arr: Array<number> = [1, 2, 3, 4]; const arr2: Array<string> = ['hello', 'world'];
注*:这种语法可能会与 React 中的 JSX 语法糖有所冲突。
-
数组类型缩写
const arr3: number[] = [1, 2, 3, 4];
const arr4: string[] = ['hello', 'world'];
只读数组的声明
如果想设置只读数组,还可以使用 flow 还可以对数组进行特殊声明,就是使用关键字 $ReadOnlyArray<T>
,如:
// readOnly
const readonlyArray: $ReadOnlyArray<number> = [1, 2, 3];
readonlyArray[1] = 20; // Error!
需要注意的是,这里的 $ReadOnly
中如果放的是对象,那么对象的属性还是可以修改的。
flow 对象类型
对象也是日常开发中日常高频使用结构,它的申明和 对象字面量 的声明还有些相似,语法为:const obj: {key: type};
const obj: { key: string } = { key: 'str' };
const obj2: {
key: string,
value: string,
} = {
key: 'unique Key',
value: 'some other value',
};
Object 的其他特性和用法还有很多,这里主要挑选几个日常开发中用的上,或者可能会碰到的一些地方简单列举一下其他用法。
Object 键值对数量未知时
日常开发中所获取的键值对的数量未知,可能会经常需要动态地添加任意数量的键值对的情况,就没有办法明确声明了。对于这种情况,flow 允许使用使用数组声明去解决这个问题。
// 表明 key 是数组类型,并且允许多个key的动态添加
// 这里表示 键值 的类型都必须要是字符串
const o: { [string]: string } = {};
o['key1'] = 'val1';
o['key2'] = 'val2';
// 为了方便写文档,也可以标记一个变量名表示一下需要的是什么类型
const userlist: { [userid: string]: string } = {};
userlist['01234567'] = 'john';
userlist['01234568'] = 'hannah';
Object 确定多个键值对类型
很多情况下,如果使用的是传统的 MVC 结构,那么 Model 里面会有不少的重复对象属性。
// 很多model中都会有用户id,然后id属性是数字
const employee: {
id: number,
username: string,
firstName: string,
lastName: string,
address: {
address1: string,
address2: string,
state: string,
},
} = {
id: 1,
username: 'test',
firstName: 'john',
lastName: 'doe',
address: {
address1: 'some street',
address2: 'some other street',
state: 'shanghai',
},
};
const employeeRelative: {
username: string,
firstName: string,
lastName: string,
address: {
address1: string,
address2: string,
state: string,
},
} = {
username: 'test',
firstName: 'mary',
lastName: 'doe',
address: {
address1: 'some street',
address2: 'some other street',
state: 'shanghai',
},
};
这种情况下就会有很多重复的声明,不过 flow 也提供了一个解决方案,就是使用准确对象类型(exact object types)去解决。通过准确对象类型,上面的代码可以重构为:
type idT = {| id: number |};
type usernameT = {| username: string |};
type firstnameT = {| firstname: string |};
type lastnameT = {| lastname: string |};
// 一般来说address都是固定类型,所以东西就放里面了
type addressT = {| address1: string, address2: string, state: string |};
type employeeT = {|
...idT,
...usernameT,
...firstnameT,
...lastnameT,
address: { ...addressT },
|};
const employee2: employeeT = {
id: 10,
username: 'test2',
firstname: 'tom',
lastname: 'sawyer',
address: {
address1: 'some street',
address2: 'some other street',
state: 'shanghai',
},
};
type emplRelativeT = {|
...usernameT,
...firstnameT,
...lastnameT,
address: { ...addressT },
|};
const relative2: emplRelativeT = {
username: 'test relative2',
firstname: 'rose',
lastname: 'sawyer',
address: {
address1: 'some street',
address2: 'some other street',
state: 'shanghai',
},
};
如果要使用 JavaScript 对 Model 层进行封装的话,确实是用 type 会方便很多。
flow 函数类型
主要指的是函数的参数和返回值的限制,这里在开始的时候也已经说过了基础的用法,现在再来复习一下:
// 注意这里标注 返回值 所在的地方,是在参数的后面
const sum = (a: number, b: number): number => {
return a + b;
};
// 函数式写法
function sum2(a: number, b: number): number {
return a + b;
}
// 不返回任何值时使用void关键字
const noReturn = (): void => {
console.log('void');
};
这里再着重讲一下对回调函数的限制应该怎么加,毕竟 JavaScript 应用最广范的特性就是回调函数了:
// 表示回调函数的第一个参数是 error
// 第二个是 value,value的值可以为 string 或是 null
// 回调函数的返回类型为 undefined
function method(callback: (error: Error | null, value: string | null) => void) {
// ...
}
flow mixed,any 和 maybe
可以让 flow 的语法更为灵活的几种用法。
mixed
一般来说,如果参数可能为多种类型,并且类型已知的情况下,可以使用关键字 |
来解决这个问题。这个关键字表示变量可能是几种类型之一,如:
function method(callback: (error: Error | null, value: string | null) => void) {
// ...
}
这里的 value 可能是 string 或是 null。
或者,也可以使用关键词 mixed,如:
const res = (value: mixed) => {
if (typeof value === 'string') {
} else if (typeof value === 'number') {
}
};
需要注意的是,如果使用了 mix 关键字而又不做类型判断,flow 是会抛出错误的:
any
any 和 mixed 有一定的相似之处,但是,flow 的官方建议谨慎使用 any,因为使用 any 关键字代表完全不会去做任何的类型检查。
flow 官方建议只有在两种情况下使用 any:
- 你知道你的代码是怎么工作的,而且你确信你的代码的行为会如同预期一般。但是出于未知原因,使用 flow 会造成异常报错时;
- 当你在使用 flow 转换代码的过程中,当前代码的类型推断可能依赖于其他尚未被实现的代码时。
maybe
使用关键字 ?type
来进行判断,以下面的代码为例:
function isNumber(value: ?number) {
// ...
}
这里的 value 可以是 3 种类型:number, null 和 undefined。使用 maybe 就代表着除了指定的类型之外,还能接受 null 和 undefined。
其他类型
其他开发能够用得上的类型有:
-
class
如果用的是比较新的写法,可以参考一下 class 的使用
-
tuple
如果知道传递的参数的长度与类型时,可以考虑使用 tuple 去做更加细粒度的限制
-
interface
-
generic
-
…
这些都在官方文档上:flow Documentation 有列举。
以上是关于flow 使用详解 + 小结的主要内容,如果未能解决你的问题,请参考以下文章
进阶学习12:Flow——JavaScript的类型检查器(安装&使用Flow详解)
Postman Flow低代码体验说明,解析常用Block使用
微软AutoMatePostman Flow低代码编排体验分享
微软AutoMatePostman Flow低代码编排如何颠覆未来IT服务