使用 Flow 寫更好的 JavaScript 代碼
你是不是常常發現自己在跟蹤代碼中的一個 bug,最后發現的錯誤只是某些本應該可以避免的簡單問題呢? 可能你只是按照錯誤的順序傳遞了參數,或者也許是你在嘗試傳遞一個字符串而不是一個數字?JavaScript 的弱類型系統,以及試著將變量在不同類型間強制轉換的意圖可能就是一整類 bug 的來源,而這些 bug 并不會存在于靜態類型語言之中。
Flow 是一個用于 JavaScript 的靜態類型檢查器,最早由 非死book 在 2014 年的 Scale 大會 上推出。它的目標是不需要開發人員去頻繁的修改實際代碼,就能找到 JavaScript 代碼中的類型錯誤,從而大大節省這方面時間與精力的消耗。同時,它也向 JavaScript 增加了額外的語法,以提供給開發者更多的控制能力。
在本文中,我會向你介紹 Flow 以及它的主要特性。我們將介紹如何設置它,如何在代碼中添加類型注釋,以及如何在運行代碼時自動刪除這些注釋。
安裝
Flow 目前可運行于 Mac OS X, Linux (64 位) 以及 Windows (64 位) 系統上。安裝它最簡單的辦法就是通過 npm:
npm install --save-dev flow-bin
然后將它添加到工程的 package.json 文件, 就在 scripts 部分下面:
"scripts": {
"flow": "flow"
}
完成了此項工作,我們就做好了繼續探索其功能的準備。
入門
一個被命名為 .flowconfig 的配置文件必須被放在工程文件的根目錄之下。通過運行如下命令,我們可以創建出一個空的配置文件:
npm run flow init
放好了這個配置文件,你就能在終端上執行如下命令,來在工程文件夾或者任意子目錄下的代碼上運行 ad-hoc 檢查了:
npm run flow check
不過,這并非使用 Flow 的最有效方式,因為這樣做會導致 Flow 每次都會重新檢查整個工程文件結構。所以我們可以使用 Flow 服務器。
Flow 服務器會對文件進行增量檢查,意思就是它只會檢查被修改了的部分。服務器可以通過在終端上執行 npm run flow 命令來啟動。
在你第一次運行該命令的時候,服務器將會啟動并顯示初始的測試結果。該操作運行很快并且是增量的工作流。每次在你想要知道測試結果的時候,就在終端上運行 flow。完成了編碼環節之后,可以使用 npm run flow stop 來終止服務器的運行。
Flow 的類型檢查是 opt-in 的。這就意味著你無須一次性完成對所有代碼的檢查。Flow 只會為你檢查那些你想要檢查的那些文件,怎么讓 Flow 知道你想要檢查哪些文件呢?只需要在這些 JavaScript 文件頂部添加 @flow 作為注釋就可以了:
/*@flow*/
這有利于將 Flow 集成到一個現有工程中,因為你可以選擇你想要檢查的文件,然后逐步解決所發現的問題。
類型推斷
通常,類型檢查有以下兩種方法:
-
通過注解: 我們指定期望的類型并把它作為代碼的一部分,類型檢查器會基于這些期望來對代碼進行評估。
-
通過代碼推斷: 工具聰明到可以通過查看代碼被使用的上下文來推斷出類型,并基于此對代碼進行檢查。
使用注解的話,我們就得編寫一些額外的代碼,它們只會在開發期間被用到,最終在瀏覽器加載的 JavaScript 構建版本中被去掉。這樣做會增加一些額外的工作,也就是通過加入額外的類型注解來讓代碼“聽話”。
而在第二個場景中,代碼隨時“待命”, 故而縮減了程序員的工作量。我們不需要對代碼進行修改,因為它會自動地推斷出表達式的數據類型。這就是 類型推斷 , 它是 Flow 最重要的特性之一。
為了描述該特性,我們來看看如下示例:
/*@flow*/
function foo(x) {
return x.split(' ');
}
foo(34);
這段代碼會在你運行 npm run flow 命令的時候在終端上報錯, 因為函數 foo() 需要的是一個字符串作為參數,而我們傳入的卻是一個數字。
報錯提示大概如下:
index.js:4
4: return x.split(' ');
^^^^^ property `split`. Property not found in
4: return x.split(' ');
^ Number
它明確說明了錯誤的位置和發生的原因。當我們將參數從數字修改為字符串,報錯就會消失,如下:
/*@flow*/
function foo(x) {
return x.split(' ');
};
foo('Hello World!');
上面的代碼就不會報錯。在這里我們能發現的就是 Flow 理解了 split() 方法只適用于字符串,所以適合的 x 就必須是一個字符串。
可為空類型
Flow 不像其它的類型系統一樣對待 null。它不會忽略掉 null,因此它可以防范那些作為其他類型傳入的 null 可能會導致應用程序崩潰的問題。
看看如下代碼:
/*@flow*/
function stringLength (str) {
return str.length;
}
var length = stringLength(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', 42);
Flow 在上面的代碼中不會發現任何錯誤,因為 + (加) 操作可以被用在字符串和數字之上, 而我們并沒有將add()的參數指定為數字。
在這種情況下,我們可以使用類型注解來指定想要的行為。Type 注解前綴一個 : (冒號),可以被放在函數參數、返回類型以及變量聲明之上。
如果我們向上面的代碼添加了類型注解,那么它就將如下所示:
/*@flow*/
function foo(x : number, y : number) : number {
return x + y;
}
foo('Hello', 42);
這段代碼會顯示一個錯誤,因為函數希望得到的是一個數字作為參數,而我們提供的卻是一個字符串。
顯示在終端上的錯誤信息如下所示:
index.js:7
7: foo('Hello', 42);
^^^^^^^ string. This type is incompatible with the expected param type of
3: function foo(x : number, y : number) : number{
^^^^^^ number
如果我們傳入的是一個數字而非“Hello”,就不會報錯了。類型注解對在龐大且復雜的 JavaScript 文件中指定想要的行為也同樣有用。
基于前面的例子,讓我們來看看 Flow 支持的一些其它的類型注解。
函數
/*@flow*/
/*--------- Type annotating a function --------*/
function add(x : number, y : number) : number {
return x + y;
}
add(3, 4);
上面的代碼展示了一個變量和一個函數的注解。add()函數的參數以及返回值都被期望是數字的。如果傳入了任何其它的數據類型, Flow 就會拋出一個錯誤。
數組
/*-------- Type annotating an array ----------*/
var foo : Array<number> = [1,2,3];
數組注解采用 Array<T> 的形式,其中的 T 表示數組中單個元素的數據類型。在上述代碼中,foo應該是一個元素為數字的數組。
類
如下是類和對象的一個示例模式。唯一需要牢記的一點是我們可以使用 | 符號在兩個類型間執行 OR 操作。變量 bar1 被加上了注解,期望的是 Bar 類的模式。
/*-------- Type annotating a Class ---------*/
class Bar{
x:string; // x should be string
y:string | number; // y can be either a string or a number
constructor(x,y){
this.x=x;
this.y=y;
}
}
var bar1 : Bar = new Bar("hello",4);
來自:https://www.oschina.net/translate/writing-better-javascript-with-flow