Flow 常用知識點整理

mufengsm發表於2019-03-31

Flow入門初識

Flow是facebook出品的JavaScript靜態型別檢查工具。
由於JavaScript是動態型別語言,它的靈活性也會造成一些程式碼隱患,使用Flow可以在編譯期儘早發現由型別錯誤引起的bug,這種方式非常有利於大型專案原始碼的開發和維護。

一、 Flow的安裝

npm install --g flow-bin

建立一個專案資料夾./demo進入專案資料夾。

npm init -y建立package.json檔案,在檔案中的scripts中新增:

"scripts": {
    "flow": "flow"
}

這就完成了然後開始正式使用。

二、正式學習

1、通過npm run flow init命令會在專案資料夾的根目錄建立一個.flowconfig檔案。

2、通過npm run flow check命令可以在你的專案根目錄以及任何子目錄資料夾下進行專門的型別檢查,但是,這並不是最高效的使用方式,因為每次Flow都會重新檢查整個專案的所有檔案,開發過程中,推薦啟動Flow服務。

3、通過npm run flow命令啟動Flow服務,Flow服務的工作方式是增量檢查也就是說它只檢查變化的部分,首次執行該命令時,服務啟動並且顯示最初型別檢查結果,這保證了Flow更高效的增量式工作流,然後接下來每次想要知道檢測結果,只要輸入flow命令即可。開發結束之後,輸入npm run flow stop停止服務。

Flow的型別檢查是可選的,並不需要一次性檢查所有程式碼。你可以選擇你想要檢查的檔案,只要在對應的JavaScript檔案最前面加上帶有@flow標識的註釋即可:

/*@flow*/
function foo(a) {
  return a;
}
fn(1);

三、型別推斷

通常,型別檢查分為以下兩種方式:

1、通過註釋:事先註釋好我們期待的型別,Flow就會基於這些註釋來評估

2、通過程式碼推斷:通過變數的使用上下文來推斷出變數型別,然後根據這些推斷來檢查型別

第一種方式,我們需要額外編寫只在開發階段起作用的程式碼,最後在程式碼編譯打包的階段被剔除。顯然,這種額外新增型別註釋的方式增加了工作量。

第二種方式,不需要任何程式碼修改即可進行型別檢查,最小化開發者的工作量。它不會強制你改變開發習慣,因為它會自動推斷出變數的型別。這就是所謂的型別推斷,Flow最重要的特性之一。

/*@flow*/
function foo(x) {
  return x.split(' ');
}
foo(34);

當你在終端執行npm run flow命令的時候,上述程式碼會報錯,因為函式foo()的期待引數是字串,而我們輸入了數字,錯誤資訊類似如下:
error_1

上述資訊清楚地指出了出錯位置和錯誤原因。我們只要將引數變成字串,即可修正錯誤,如下所示:

/*@flow*/
function foo(x) {
    return x.split(' ');
}
foo("Hello World");

split()方法只適用於string型別的變數,所以x應該是string,這就是型別推斷。


四、空型別

Flow處理null。它不會忽略null,這樣可以防止,因為給變數傳了null而導致程式崩潰的錯誤。

/*@flow*/
function stringLength(str) {
    return str.length;
}
var length = stringLength(null);

error_null

Flow會報錯。為了防止出錯,我們需要單獨處理null

/*@flow*/
function stringLength (str) {
  if (str !== null) {
    return str.length;
  }
  return 0;
}
var length = stringLength(null);

程式碼中我們引入對null的檢查,確保程式碼能在任何情況下都正常且正確執行。上述程式碼可以通過Flow的型別檢查。

五、型別註釋

型別推斷是Flow最有用的特性之一,不需要編寫型別註釋就能獲取有用的反饋。但在某些特定的場景下,新增型別註釋可以提供更好更明確的檢查依據。

/*@flow*/
function foo(x, y){
  return x + y;
}
foo('Hello', 18);

Flow檢查上述程式碼時檢查不出任何錯誤,因為+即可以用在字串上,也可以用在數字上,我們並沒有明確指出foo()的引數必須為數字。

在這種情況下,我們可以藉助型別註釋來指明期望的型別。型別註釋是以冒號:開頭,可以在函式引數,返回值,變數宣告中使用,如果我們在上段程式碼中新增型別註釋,就會變成如下:

/*@flow*/
function foo(x : number, y : number) : number {
  return x + y;
}
foo('Hello', 18);

第一個和第二個number是x和y兩個形參需要接收number型別的值,第三個number是foo()函式需要返回一個number的值

現在Flow就能檢查出錯誤,因為函式引數的期待型別為數字,而我們提供了字串,錯誤資訊:
error_lxzs

如果傳入的引數是數字,就不會有錯誤。


1、函式的型別註釋

/*@flow*/
function add(x : number, y : number) : number {
  return x + y;
}
add(3, 4);

上述程式碼展示了變數型別註釋以及函式型別註釋。函式add()的引數,以及函式的返回值,期待型別為數字。如果傳入其他型別引數,Flow就會檢測到錯誤。

2、陣列的型別註釋

/*@flow*/
var foo : Array<number> = [1,2,3];

陣列型別註釋的格式是Array<T>T表示陣列中每項的資料型別。在上述程式碼中,foo是每項均為數字的陣列。

3、類的型別註釋

下面展示了類和物件的型別註釋模型。唯一需要注意的是,可以在兩個型別之間使用邏輯,用|來間隔。變數bar1新增了必須為Bar類的型別註釋。

/*@flow*/
class Bar {
    x: string;                  
    y: string | number;  
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
}
var bar1: Bar = new Bar("hello", 4);

4、物件字面量的型別註釋

物件的型別註釋,跟類的型別註釋很像,指定物件屬性的型別。

/*@flow*/
var obj: { a: string, b: number, c: Array<string>, d: Bar } = {
    a: "hello",
    b: 42,
    c: ["hello", "world"],
    d: new Bar("hello", 3)
}

5、null的型別註釋

若想任意型別T可以為null或者undefined,只需類似如下寫成?T的格式即可。

/*@flow*/
var foo: ?string = null;

此時,foo可以為字串,也可以為null

型別註釋官方文件


六、庫的定義

我們經常需要引入第三方庫,Flow檢查時就會丟擲錯誤。但這並不是我們期待的錯誤。

慶幸的是,我們不需要修改庫原始碼去防止這些報錯。我們只需建立一個庫定義(libdef)。libdef是包含第三方庫宣告的JS檔案簡稱。

觀察下面的例子:

/* @flow */
var users = [
  { name: 'John', designation: 'developer' },
  { name: 'Doe', designation: 'designer' }
];
function getDeveloper() {
  return _.findWhere(users, {designation: 'developer'});
}

會報錯:
error_ku

由於Flow並不認識$,所以會報錯。要解決這個問題,我們需要引入jQuery的庫定義。

使用flow-typed

通過npm install -g flow-typed安裝flow-typed倉庫,它包含了眾多流行的第三方庫的libdef。只需在專案根目錄下建立一個名為flow-typed的資料夾,並且下載相關的定義檔案即可。

安裝成功之後, 執行flow-typed install來檢查package.json檔案,並且下載所有專案中用到的第三方庫的libdef。
等待的時間有點久,等下載完後,再npm run flow,就會發現沒有錯誤了。

自定義libdef

如果你用的庫並不在flow-typed倉庫,你可以建立你自己的libdef,感興趣可以檢視自定義libdef

七、剔除型別註釋

由於額外新增的型別註釋不是正確的JavaScript語法,打包編譯的時候需要在原始碼中剔除。可以通過flow-remove-types來剔除,或者如果你已經用Babel來轉譯JS,你可以使用Babel preset來移除。我們只討論第一種方法。

首先需要安裝flow-remove-types作為專案依賴庫:npm install --save-dev flow-remove-types

然後在package.json檔案中新增另一個script入口:

"scripts": {
  "flow": "flow",
  "build": "flow-remove-types src/ -d dist/"
}

執行npm run build將剔除src資料夾下的所有型別註釋,在dist資料夾中儲存編譯後的版本。編譯後的檔案就是普通的能執行於瀏覽器的JavaScript檔案。

//編譯前,/* @flow */記得寫。
/* @flow */
function fn1(x :number) {
    return x;
}
fn1(1)
//編譯後
/*       */
function fn1(x        ) {
    return x;
}
fn1(1)

結束語

本文討論了Flow各種各樣的型別檢查特性,展示了Flow如何幫助我們捕獲錯誤提高程式碼質量。我們也看到了如何用可選的方式去逐個檢查JS檔案,如何做型別推斷。

參考於:用Flow編寫更好的JavaScript程式碼

本人的部落格
本人的github
本人的郵箱:scarf666@163.com
本文章配合TypeScript食用更佳

相關文章