JS静态类型检查 Flow【实践】

博客内容,仅为个人理解实践,如有错误,敬请指正。

实践理解

一:什么是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类型注释

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';

更多更详细请参考官方文档

猜你喜欢

转载自blog.csdn.net/jw2268136570/article/details/105347456