flow 使用详解 + 小结

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 只能存储 truefalse

    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 中,它有两种声明方式:

  1. 使用 Array<Type> 语法

    const arr: Array<number> = [1, 2, 3, 4];
    const arr2: Array<string> = ['hello', 'world'];
    

    注*:这种语法可能会与 React 中的 JSX 语法糖有所冲突。

  2. 数组类型缩写

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:

  1. 你知道你的代码是怎么工作的,而且你确信你的代码的行为会如同预期一般。但是出于未知原因,使用 flow 会造成异常报错时;
  2. 当你在使用 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服务

从 Fragment 返回时,Flow onEach/collect 被多次调用

如何将三万行代码从 Flow 移植到 TypeScript?