博客内容,仅为个人理解实践,如有错误,敬请指正。
实践理解
一:什么是flow?
- flow是JavaScript的静态类型检查工具,它定义了一套语法(主要体现在 约束JavaScript 原有类型,借鉴Java 类型)并在预编译期间对类型进行编译检查以提高JavaScript程序运行时的健壮性。
二:JavaScript和Java
- JavaScript是一种动态弱类型的解释型语言。但是如果我们不直接交给浏览器运行,而是使用了babel或者babel-flow等工具对代码进行预编译,它就从解释型语言变成了编译解释型语言。
- Java是一种静态强类型的编译解释型语言。
动态语言的优势
- 思维不受束缚,可以任意发挥,把更多的精力放在产品本身上;
- 集中思考业务逻辑实现,思考过程即实现过程;
静态语言的优势
- 由于类型的强制声明,使得IDE有很强的代码感知能力,故,在实现复杂的业务逻辑、开发大型商业系统、以及那些生命周期很长的应用中,依托IDE对系统的开发很有保障;
- 由于静态语言相对比较封闭,使得第三方开发包对代码的侵害性可以降到最低;
三:flow约束了JavaScript的哪些 原有类型?
- 约束JavaScript作为弱类型语言而导致过于灵活的变量赋值与形实参匹配
- 约束JavaScript的boolean类型过于灵活的真假值(隐式转换boolean)
- 约束JavaScript的string类型过于灵活的字符串连接符(字符串可和基本上所有的类型相加)
- 约束JavaScript的object类型过于灵活的对象结构(不基于类、对象不封闭)
- 约束JavaScript的array类型过于灵活的数组结构(稀疏数组、索引越界)
- …
四:flow借鉴了Java的哪些 类型?
- 变量/形参/返回值 类型
- 枚举类型
- 泛型
- 元组类型(也许取自Python)
- 更加结构性的类和对象
- 接口类型
- …
实践:项目引入flow
一:初始化项目
1.新建项目:flow-test
2.新建package.json
3.新建目录结构:/src/js、/dist
二:引入flow
此处Package Managers选择npm,Compilers选择Babel,其他方式参考https://flow.org/en/docs/install/
1.安装compiler
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
2.安装flow
- 安装
// 本地安装
npm install --save-dev flow-bin
// 全局安装
npm install -g flow-bin
3.项目配置
- 手动创建babel配置文件:.babelrc
{
"presets": ["@babel/preset-flow"]
}
- 自动创建flow配置文件:.flowconfig
npm run flow init
- 配置脚本以便运行:package.json
"scripts": {
"build": "babel src/js/ -d dist/",
"flow": "flow"
}
- 测试运行(注意:亲测,执行以下命令时,如果项目路径中包含中文则会报错找不到文件夹)
npm run flow
三:flow工作流
- 1.flow init:初始化flow配置文件
- 2.flow status:启动flow后台进程,监视所有flow文件(flow stop停止后台进程)
- 3.// @flow:将需检查的JavaScript文件设置为flow文件
- 4.write flow code,如(webstorm设置JavaScript版本为flow,防止代码提示报错)
// @flow
function foo(x: ?number): string {
if (x) {
return x;
}
return "default string";
}
- 5.flow:检查所有flow文件(如果后台进程没有启动则自动启动)
实践:flow类型注释
一:基本类型
JavaScript 的包装类型匹配(Java认可,flow不认可)
- Java支持装箱 / 拆箱匹配
package test;
public class Test {
private static void test1(boolean a){
}
private static void test2(Boolean a){
}
public static void main(String[] args){
test1(true);// work! 正常匹配
test1(new Boolean(true));// work! 拆箱后匹配
test2(true);// work! 装箱后匹配
test2(new Boolean(true));// work! 正常匹配
}
}
- flow不支持装箱 / 拆箱匹配(基本数据类型与其包装类型不匹配)
// @flow
function test1(a: boolean) {
}
function test2(a: Boolean) {
}
test1(true); // Works!
test1(new Boolean(true)); // Error!
test2(true); // Error!
test2(new Boolean(true)); // Works!
boolean:JavaScript语言的 “真假值”(隐式转换boolean)【flow不认可】
- 内置函数与构造器的输出结果区别
- flow仅支持boolean类型互相匹配,不支持“真假值”匹配
// @flow
function test(a: boolean) {
}
test(true); // Works!
test(1); // Error!
test(Boolean(1)); // Works!
test(!!1); // Works!
number:JavaScript 的特殊数值类型【flow认可】
// @flow
function test(a: number) {
}
test(42); // Works!
test(3.14); // Works!
test(NaN); // Works!
test(Infinity); // Works!
test("foo"); // Error!
string:JavaScript的 字符串连接符+【flow部分认可】
- JavaScript本身支持
- flow仅认可字符串与数值相加,字符串与其它类型相加不被认可
let a = "foo" + "foo"; // Works!
let b = "foo" + 42; // Works!
let b1 = "foo" + true; // Error!
let b2 = "foo" + null; // Error!
let b3 = "foo" + undefined; // Error!
let c = "foo" + []; // Error!
let d = "foo" + [].toString(); // Works!
let e = "foo" + {
}; // Error!
let f = "foo" + String({
}); // Works!
let g = "foo" + JSON.stringify({
}) ; // Works!
null和void(匹配undefined):JavaScript独特的数据类型,null和undefined【flow认可且区分】
// @flow
function acceptsNull(value: null) {
}
function acceptsUndefined(value: void) {
}
acceptsNull(null); // Works!
acceptsNull(undefined); // Error!
acceptsUndefined(null); // Error!
acceptsUndefined(undefined); // Works!
symbol:JavaScript的symbol类型【flow认可】
// @flow
function acceptsSymbol(value: symbol) {
// ...
}
acceptsSymbol(Symbol()); // Works!
acceptsSymbol(Symbol.isConcatSpreadable); // Works!
acceptsSymbol(false); // Error!
二:直接量和枚举类型
number、string、boolean:支持数字、字符串、布尔直接量
// @flow
function acceptsNumberTwo(value: 2) {
}
function acceptsStringM(value: 'm') {
}
function acceptsBooleanTrue(value: true) {
}
acceptsNumberTwo(2); // Works!
acceptsNumberTwo(3); // Error!
acceptsNumberTwo("2"); // Error!
acceptsStringM('m'); // Works!
acceptsStringM('n'); // Error!
acceptsStringM(1); // Error!
acceptsBooleanTrue(true); // Works!
acceptsBooleanTrue(false); // Error!
acceptsBooleanTrue(1); // Error!
Enum(枚举类型):直接量 + | 运算符 实现枚举效果
- 以下形参构成枚举类型[“success” , “warning” , “danger”]
// @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!
getColor("error"); // Error!
三:混合类型
|:或运算符
function stringifyBasicValue(value: string | number) {
return '' + value;
}
T:泛型
// @flow
function identity<T>(value: T): T {
return value;
}
identity<string>('m'); // Work!
identity<number>(1); // Work!
identity<boolean>(true);// Work!
mixed:任意类型
// @flow
function stringify(value: mixed) {
// ...
}
stringify("foo");
stringify(3.14);
stringify(null);
stringify({
});
四:也许类型
- ?type:除可接受指定类型之外,还可接收undefined、null或不传。
// @flow
function acceptsMaybeNumber(value: ?number) {
// ...
}
acceptsMaybeNumber(42); // Works!
acceptsMaybeNumber(); // Works!
acceptsMaybeNumber(undefined); // Works!
acceptsMaybeNumber(null); // Works!
acceptsMaybeNumber("42"); // Error!
五:变量类型
- const变量是不可再赋值的,自动推导和提供类型的结果是始终一致的。
// @flow
const foo /* : number */ = 1;
const bar: number = 2;
提供类型
// @flow
let barLet: number = 2;
barLet = 3; // Work!
barLet = '4'; // Error
自动推导(检查时某些情况下会误报)
- 自动推导正确
// @flow
let foo = 42;
let isNumber: number = foo; // Works!,foo此时为数字类型
foo = true;
let isBoolean: boolean = foo; // Works!,foo此时为布尔类型
foo = "hello";
let isString: string = foo; // Works!,foo此时为字符串类型
- 自动推导错误
// @flow
let foo = 42;
function mutate() {
foo = true;
foo = "hello";
}
mutate();
let isString: string = foo; // Error!,检测误报,经过mutate函数后,foo为字符串类型,此句本应Works!
六:函数类型
参数、返回值类型
// @flow
function concat(a: string, b: string): string {
return a + b;
}
concat("foo", "bar"); // Works!
// $ExpectError
concat(true, false); // Error!
param?:可选参数
- 实参可为undefined或缺失,但不能为null
// @flow
function acceptsOptionalString(value?: string) {
// ...
}
acceptsOptionalString("bar"); // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null); // Error!
acceptsOptionalString(); // Works!
param = default:形参默认值
- 实参可为undefined或缺失,但不能为null
// @flow
function acceptsOptionalString(value: string = "foo") {
// ...
}
acceptsOptionalString("bar"); // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null); // Error!
acceptsOptionalString(); // Works!
…参数:rest可变参数
// @flow
function method(...args: Array<number>) {
// ...
}
method(); // Works.
method(1); // Works.
method(1, 2); // Works.
method(1, 2, 3); // Works.
method(1, 2, 3,'4'); // Error.
…
七:对象类型
对象属性类型
// @flow
var obj1: {
foo: boolean } = {
foo: true };
var obj2: {
foo: number,
bar: boolean,
baz: string,
} = {
foo: 1,
bar: true,
baz: 'three',
};
可选属性
// @flow
function acceptsObject(value: {
foo?: string }) {
// ...
}
acceptsObject({
foo: "bar" }); // Works!
acceptsObject({
foo: undefined }); // Works!
// $ExpectError
acceptsObject({
foo: null }); // Error!
acceptsObject({
}); // Works!
封闭对象(属性类型个数确定):已有属性的对象视为封闭对象
// @flow
var obj = {
foo: 1
};
obj.bar = true; // Error!
obj.baz = 'three'; // Error!
未封闭对象(可修改属性类型 / 个数):空对象视为未封闭对象
// @flow
var obj = {
};
obj.foo = 1; // Works!
obj.bar = true; // Works!
obj.baz = 'three'; // Works!
…
八:数组类型
指明数组元素类型
let arr1: Array<boolean> = [true, false, true];
let arr2: Array<string> = ["A", "B", "C"];
let arr3: Array<mixed> = [1, true, "three"]
// 简写
let arr: number[] = [0, 1, 2, 3];
稀疏数组和索引越界问题(flow并不能检查)
- 手动判断
let array: Array<number> = [0, 1, 2];
let value: number | void = array[1];
if (value !== undefined) {
// number
}
…
九:元组类型
// @flow
let tuple: [number, boolean, string] = [1, true, "three"];
let num : number = tuple[0]; // Works!
let bool : boolean = tuple[1]; // Works!
let str : string = tuple[2]; // Works!
let none = tuple[3]; // Error!,能够检查越界问题
与Python元组的比较
- 同:flow元组与Python元组的元素类型和个数都不可变。
- 异:flow元组元素值可变(同一类型下),Python元素值不可变。
…
十:类类型
属性
- 正确示例
// @flow
class MyClass {
prop: number;
method() {
this.prop = 42;
}
}
- 错误示例
// @flow
class MyClass {
method() {
// $ExpectError
this.prop = 42; // Error!
}
}
静态属性
// @flow
function func_we_use_everywhere (x: number): number {
return x + 1;
}
class MyClass {
static constant: number;
static helper: (number) => number;
method: number => number;
}
MyClass.helper = func_we_use_everywhere
MyClass.constant = 42
MyClass.prototype.method = func_we_use_everywhere
类属性
class MyClass {
// prop: number = 42;
prop = 42;// 自动推导
}
类泛型
// @flow
class MyClass<A, B, C> {
constructor(arg1: A, arg2: B, arg3: C) {
// ...
}
}
var val: MyClass<number, boolean, string> = new MyClass(1, true, 'three');
类实例
- 规范创建对象
//@flow
class MyClass {
}
(MyClass: MyClass); // Error,不能省略new和()
(new MyClass(): MyClass); // Ok
十一:类型别名
类型重用
// @flow
type MyObject = {
// ...
};
var val: MyObject = {
/* ... */ };
function method(val: MyObject) {
/* ... */ }
class Foo {
constructor(val: MyObject) {
/* ... */ } }
常用类型别名:枚举类型、对象类型
type UnionAlias = 1 | 2 | 3;// 枚举类型
type ObjectAlias = {
// 对象类型
property: string,
method(): number,
};
类型别名泛型
// @flow
type MyObject<A, B, C> = {
foo: A,
bar: B,
baz: C,
};
var val: MyObject<number, boolean, string> = {
foo: 1,
bar: true,
baz: 'three',
};
十二:接口类型
仿Java接口
// @flow
interface Serializable {
serialize(): string;
}
class Foo implements Serializable {
serialize() {
return '[Foo]'; } // Works!
}
class Bar implements Serializable {
// $ExpectError
serialize() {
return '[Bar]'; } // Works!
}
class Bar2 implements Serializable {
// $ExpectError
serialize() {
return 42; } // Error!
}
const foo: Foo = new Bar(); // Error!
const foo2: Serializable = new Foo(); // Works!
const bar: Serializable = new Bar(); // Works!
…
十三:联合类型
枚举直接量类型
枚举枚举直接量类型
type Numbers = 1 | 2;// 枚举直接量类型
type Colors = 'red' | 'blue' // 枚举直接量类型
type Fish = Numbers | Colors;// 枚举枚举直接量类型
…
十四:交叉点类型
// @flow
type A = {
a: number };
type B = {
b: boolean };
type C = {
c: string };
function method(value: A & B & C) {
// ...
}
// $ExpectError
method({
a: 1 }); // Error!
// $ExpectError
method({
a: 1, b: true }); // Error!
method({
a: 1, b: true, c: 'three' }); // Works!
十五:typeof取类型
// @flow
let num1 = 42;
let num2: typeof num1 = 3.14; // Works!
// $ExpectError
let num3: typeof num1 = 'world'; // Error!
let bool1 = true;
let bool2: typeof bool1 = false; // Works!
// $ExpectError
let bool3: typeof bool1 = 42; // Error!
let str1 = 'hello';
let str2: typeof str1 = 'world'; // Works!
// $ExpectError
let str3: typeof str1 = false; // Error!
导入时取类型
// @flow
import typeof myNumber from './exports';
import typeof {
MyClass} from './exports';