Flow:Facebook 的 JavaScript 靜態類型檢查器

記某年的一次團隊分享,主要目的:優化又臭又長維護噩夢的JavaScript老項目html

JavaScript寫起來,行雲流水、揮灑自如、無拘無束、筆走龍蛇、隨心所欲react

金主粑粑,天天抓狂,小修小補的hotfix從未中止脆弱的代碼經不住半點風浪webpack

Flow是JavaScript代碼的靜態類型檢查器。 它能夠幫助您提升工做效率。 讓您的代碼更快,更智能,更自信,更大規模git

Flow經過靜態類型註釋檢查代碼是否存在錯誤。 這些類型容許您告訴Flow您但願代碼如何工做,Flow將確保它以這種方式工做。github

1.從demo開始認識flow

2.安裝,配置

3.flow總結及使用

前言

咱們知道react源碼如今仍是採用flow + js的方式,下圖截取一小段react Fiber源碼,先混個臉熟web

/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */
import type {ReactElement} from 'shared/ReactElementType';
import type {ReactFragment, ReactPortal, ReactScope} from 'shared/ReactTypes';
import type {Fiber} from './ReactInternalTypes';
import type {RootTag} from './ReactRootTags';
import type {WorkTag} from './ReactWorkTags';
import type {TypeOfMode} from './ReactTypeOfMode';
import type {Lanes} from './ReactFiberLane.new';
import type {SuspenseInstance} from './ReactFiberHostConfig';
import type {OffscreenProps} from './ReactFiberOffscreenComponent';
複製代碼

1.從demo開始認識flow

1.1 出入參靜態類型註釋

// @flow
function square(n: number): number {
  return n * n;
}

square("2"); // Error!
複製代碼

報錯信息:正則表達式

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

Cannot call square with '2' bound to n because string [1] is incompatible with number [2].編程

1.2.運算結果類型檢查

由於Flow很好地理解JavaScript,因此它不須要不少這些類型。 你應該只須要作不多的工做來描述你的Flow代碼,它將推斷其他部分。 在不少時候,Flow能夠徹底理解您的代碼而不須要任何類型json

// @flow
function square(n) {
  return n * n; // Error!
}

square("2");
複製代碼

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

2.安裝

2.1 安裝編譯器

官方推薦babelflow-remove-types

npm install --save-dev @babel/cli @babel/preset-flow
複製代碼

項目增長babel.config.js文件

module.exports = function() {
  return {
    presets: [
      "@babel/preset-flow"
    ]
  }
}
複製代碼

package.json中添加scripts

{
  "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

npm install --save-dev flow-bin
複製代碼

package.json中添加scripts

{
  "devDependencies": {
    "flow-bin": "^0.99.0"
  },
  "scripts": {
    "flow": "flow"
  }
}
複製代碼

生成flowconfig配置文件

npm run flow init
複製代碼

運行flow

npm run flow
複製代碼

3.flow總結及使用

3.1 使用 flow init 初始化項目

生成相似INI格式,項目.flowconfig配置文件

3.1.1 .flowconfig由6個部分組成

; 忽略匹配文件
[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

# 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

flow status // 啓動flow後臺進程
flow stop   // 終止flow後臺進程
複製代碼

webpack熱加載,使用flow-webpack-plugin

'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

// @flow

或

/* @flow */
複製代碼

忽略//@flow,檢查全部文件

flow check --all
複製代碼

3.4 編寫flow代碼

Flow後臺進程將會捕獲此錯誤

// @flow

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

3.5 檢查代碼是否存在類型錯誤

# equivalent to `flow status`
flow
複製代碼

運行flow檢查

// @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 如何在代碼中添加類型註釋

類型註釋符號

|       // 或
&       // 且 
?       // 可選
複製代碼

類型註釋中包括的類型

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")
複製代碼

小寫

// @flow
function method(x: number, y: string, z: boolean) {
  // ...
}

method(3.14, "hello", true);
複製代碼

大寫

// @flow
function method(x: Number, y: String, z: Boolean) {
  // ...
}

method(new Number(42), new String("world"), new Boolean(false));
複製代碼

boolean

// @flow
function acceptsBoolean(value: boolean) {
  // ...
}

acceptsBoolean(true);  // Works!
acceptsBoolean(false); // Works!
acceptsBoolean("foo"); // Error!
複製代碼

JavaScript能夠隱式地將其餘類型的值轉換爲布爾值

if (42) {} // 42 => true
if ("") {} // "" => false
複製代碼

非布爾值須要顯式轉換爲布爾類型

// @flow
function acceptsBoolean(value: boolean) {
  // ...
}

acceptsBoolean(0);          // Error!
acceptsBoolean(Boolean(0)); // Works!
acceptsBoolean(!!0);        // Works!
複製代碼

string

// @flow
function acceptsString(value: string) {
  // ...
}

acceptsString("foo"); // Works!
acceptsString(false); // Error!
複製代碼

JavaScript能夠隱式地將其餘類型的值轉換爲字符

"foo" + 42; // "foo42"
"foo" + {}; // "foo[object Object]"
複製代碼

Flow鏈接到字符串時只接受字符串和數字。

// @flow
"foo" + "foo"; // Works!
"foo" + 42;    // Works!
"foo" + {};    // Error!
"foo" + [];    // Error!
複製代碼

必須明確並將其餘類型轉換爲字符串

// @flow
"foo" + String({});     // Works!
"foo" + [].toString();  // Works!
"" + JSON.stringify({}) // Works!
複製代碼

number

// @flow
function acceptsNumber(value: number) {
  // ...
}

acceptsNumber(42);       // Works!
acceptsNumber(3.14);     // Works!
acceptsNumber(NaN);      // Works!
acceptsNumber(Infinity); // Works!
acceptsNumber("foo");    // Error!
複製代碼

null and void

// @flow
function acceptsNull(value: null) {
  /* ... */
}

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

acceptsNull(null);           // Works!
acceptsNull(undefined);      // Error!
acceptsUndefined(null);      // Error!
acceptsUndefined(undefined); // Works!
複製代碼

Array

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

// @flow
var obj1: { foo: boolean } = { foo: true };
var obj2: {
  foo: number,
  bar: boolean,
  baz: string,
} = {
  foo: 1,
  bar: true,
  baz: 'three',
};
複製代碼

Function

// @flow
function concat(a: string, b: string): string {
  return a + b;
}

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

箭頭Function

let method = (str, bool, ...nums) => {
  // ...
};

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

回調Function

function method(callback: (error: Error | null, value: string | null) => void) {
  // ...
}
複製代碼

class

// @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

// @flow
function acceptsMaybeString(value: ?string) {
  // ...
}

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

// value: string null or undefined
複製代碼

對象屬性可選

// @flow
function acceptsObject(value: { foo?: string }) {
  // ...
}

acceptsObject({ foo: "bar" });     // Works!
acceptsObject({ foo: undefined }); // Works!
acceptsObject({ foo: null });      // Error!問號放在string前不報錯
acceptsObject({});                 // Works!
複製代碼

函數參數可選

// @flow
function acceptsOptionalString(value?: string) {
  // ...
}

acceptsOptionalString("bar");     // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null);      // Error!問號放在string前不報錯
acceptsOptionalString();          // Works!
複製代碼

帶默認值的函數參數

// @flow
function acceptsOptionalString(value: string = "foo") {
  // ...
}

acceptsOptionalString("bar");     // Works!
acceptsOptionalString(undefined); // Works!
acceptsOptionalString(null);      // Error!
acceptsOptionalString();          // Works!
複製代碼

使用字面文字做爲類型

// @flow
function acceptsTwo(value: 2) {
  // ...
}

acceptsTwo(2);   // Works!
// $ExpectError
acceptsTwo(3);   // Error!
// $ExpectError
acceptsTwo("2"); // Error!
複製代碼

Union Types

// @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

function stringifyBasicValue(value: string | number) {
  return '' + value;
}
複製代碼

A type based on another type

function identity<T>(value: T): T {
  return value;
}
複製代碼

An arbitrary type that could be anything

function getTypeOf(value: mixed): string {
  return typeof value;
}
複製代碼

Any Types

// @flow
function add(one: any, two: any): number {
  return one + two;
}

add(1, 2);     // Works.
add("1", "2"); // Works.
add({}, []);   // Works.
複製代碼

變量類型 將類型添加到變量聲明 const let var

// @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

let foo: number = 1;
foo = 2;   // Works!
// $ExpectError
foo = "3"; // Error!
複製代碼

從新分配變量

let foo = 42;

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

let isOneOf: number | boolean | string = foo; // Works!
複製代碼

從新分配變量肯定變量類型

// @flow
let foo = 42;
let isNumber: number = foo; // Works!

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

foo = "hello";
let isString: string = foo; // Works!
複製代碼

react

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} />;
複製代碼

想了解更多用法,可移步flow官方文檔flow.org

此篇筆記已收錄 我的筆記,感謝閱讀,歡迎star鼓勵

相關文章
相關標籤/搜索