1. 程式人生 > >使用 Flow 寫更好的 JavaScript 程式碼

使用 Flow 寫更好的 JavaScript 程式碼

關於本文:

原文地址 翻譯地址 譯者:野草

你是否經常在debug那些簡單可避免的bug?可能你給函式傳參的時候搞錯了引數的順序,或者本來應該傳個Number型別的引數,你傳了一個String型別?JavaScript的弱型別是這一類bug的罪魁禍首,靜態型別語言中不存在此類bug。

Flow就是JavaScript的靜態型別檢查工具,由Facebook團隊於2014年的Scale Conference上首次提出。該庫的目標在於檢查JavaScript中的型別錯誤,開發者通常不需要修改程式碼即可使用,故使用成本很低。同時,它也提供額外語法支援,使得開發者能更大程度地發揮Flow的作用。

本文將介紹Flow及其主特性。下面分別從如何安裝設定,如何新增型別註釋,如何在執行時自動去掉註釋等方面來介紹。

安裝

目前Flow相容的作業系統有Mac OS X,Linux(64位),Windows(64位)。最簡單的安裝方式是通過npm:

npm install --save-dev flow-bin 
  • 1
  • 2

然後在package.json檔案中的scripts項新增:

"scripts": {
  "flow": "flow"
}
  • 1
  • 2
  • 3
  • 4

完成之後,就可以開始探索它的特性了。

啟動

首先需要在專案的根目錄下建立一個.flowconfig配置檔案。我們可以通過以下命令建立一個空配置檔案:

npm run flow init
  • 1
  • 2

完成設定之後,在終端輸入以下命令可以在你的專案根目錄以及任何子目錄資料夾下進行專門的型別檢查:

npm run flow check
  • 1
  • 2

但是,這並不是最高效的使用方式,因為每次Flow都會重新檢查整個專案的所有檔案。開發過程中,推薦啟動Flow服務。

Flow服務的工作方式是增量檢查,也就是說它只檢查變化的部分。在終端輸入以下命令來啟動Flow服務:

npm run flow
  • 1
  • 2

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

停止服務。

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

/*@flow*/
  • 1
  • 2

當你想在已有專案中加入Flow的時候,該特性特別有幫助。因為你可以一一選擇並檢測你要的檔案,然後修正錯誤。

型別推斷

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

  • 通過註釋:事先註釋好我們期待的型別,Flow就會基於這些註釋來評估
  • 通過程式碼推斷:通過變數的使用上下文來推斷出變數型別,然後根據這些推斷來檢查型別

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

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

我們來通過一個例子來說明這個特性:

/*@flow*/

function foo(x) {
  return x.split(' ');
}

foo(34);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

錯誤資訊類似如下:

index.js:4
  4:   return x.split(' ');
                ^^^^^ property `split`. Property not found in
  4:   return x.split(' ');
              ^ Number
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

/*@flow*/

function foo(x) {
  return x.split(' ');
};

foo('Hello World!');
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如上所述,以上程式碼不會報任何錯。該例想說明的是,因為split()方法只適用於string型別的變數,所以x應該是string,這就是型別推斷。

空型別

Flow處理null的方式與其他型別庫不同。它不會忽略null,這樣可以防止了因給變數傳了null而導致程式崩潰的錯誤。

思考以下程式碼:

/*@flow*/

function stringLength (str) {
  return str.length;
}

var length = stringLength(null);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

/*@flow*/

function stringLength (str) {
  if (str !== null) {
    return str.length;
  }

  return 0;
}

var length = stringLength(null);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

型別註釋

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

考慮以下程式碼:

/*@flow*/

function foo(x, y){
  return x + y;
}

foo('Hello', 42);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

在這種情況下,我們可以藉助型別註釋來指明期望的型別。型別註釋是以冒號:開頭,可以在函式引數,返回值,變數宣告中使用。

如果我們在上段程式碼中新增型別註釋,就會變成如下:

/*@flow*/

function foo(x : number, y : number) : number {
  return x + y;
}

foo('Hello', 42);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

Flow報錯資訊類似如下:

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果傳入的引數是數字,就不會有錯誤。型別註釋在大型複雜的JavaScript檔案中也很有用,它能保證程式碼按照預期進行。

記住上一個例子,我們來看看Flow能支援的其他更多型別註釋。

函式

/*@flow*/

/*--------- Type annotating a function --------*/
function add(x : number, y : number) : number {
  return x + y;
}

add(3, 4);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

陣列

/*-------- Type annotating an array ----------*/
var foo : Array<number> = [1,2,3];
  • 1
  • 2
  • 3

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

下面展示了類和物件的型別註釋模型。唯一需要注意的是,可以在兩個型別之間使用或邏輯,用|來間隔。變數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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

物件字面量

物件的型別註釋類似於類,指定物件屬性的型別。

/*--------- Type annonating an object ---------*/

var obj : {a : string, b : number, c: Array<string>, d : Bar} = {
  a : "hello",
  b : 42,
  c : ["hello", "world"],
  d : new Bar("hello",3)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Null

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

/*@flow*/

var foo : ?string = null;
  • 1
  • 2
  • 3
  • 4

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

目前我們只對Flow的型別註釋做了很淺的探索。一旦你習慣了使用這些基本型別,建議在Flow官網上的型別文件深入瞭解所有的型別。

庫定義

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

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

觀察下面的例子:

/* @flow */

var users = [
  { name: 'John', designation: 'developer' },
  { name: 'Doe', designation: 'designer' }
];

function getDeveloper() {
  return _.findWhere(users, {designation: 'developer'});
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

Flow會檢查出以下錯誤:

interfaces/app.js:9
  9:   return _.findWhere(users, {designation: 'developer'});
              ^ identifier `_`. Could not resolve name
  • 1
  • 2
  • 3
  • 4

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

使用flow-typed

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

為了進一步簡化,可以用npm的命令列方式一鍵獲取和安裝libdef檔案:

npm install -g flow-typed
  • 1
  • 2

安裝成功之後, 執行flow-typed install來檢查package.json檔案,並且下載所有專案中用到的第三方庫的libdef。

自定義libdef

如果你用的庫並不在flow-typed倉庫,你可以建立你自己的libdef。本文不會細談自定義libdef,因為很少會有人遇到,感興趣可以檢視此文件

剔除型別註釋

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

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

npm install --save-dev flow-remove-types
  • 1
  • 2

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

"scripts": {
  "flow": "flow",
  "build": "flow-remove-types src/ -D dest/",
}
  • 1
  • 2
  • 3
  • 4
  • 5

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

結語

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

你覺得Flow這種對JavaScript進行靜態型別檢查的方式如何?頗有用處,還是畫蛇添足?看了本文後,是不是躍躍欲試了?歡迎分享你的想法,說說你的疑問。

相關推薦

使用 Flow JavaScript 程式碼

關於本文: 原文地址 翻譯地址 譯者:野草 你是否經常在debug那些簡單可避免的bug?可能你給函式傳參的時候搞錯了引數的順序,或者本來應該傳個Number型別的引數,你傳了一個String型別?JavaScript的弱型別是這一類bug的罪魁禍首,靜態型別語言中

Flow編寫的js程式碼

關於本文: 原文地址 翻譯地址 譯者:野草 本文發表於前端早讀課【第897期】 你是否經常在debug那些簡單可避免的bug?可能你給函式傳參的時候搞錯了引數的順序,或者本來應該傳個Number型別的引數,你傳了一個String型別?JavaScript的弱型別是這一類bug的罪魁禍

如何的程式設計,程式碼

          在程式碼的追求上應該是——“精簡高效”“規範”“複用”“考慮全面”“異常處理”“記憶體釋取有始有終” 個人在程式設計過程,結合以上的文章總結下體會:在寫的時候應該主要站在專案的可擴充套件性、程式碼的複用性以及做到少量程式碼做更多的事的角度來寫程式碼。

如何程式碼(文末有福利)

女主宣言我們在過去的幾期推送裡已經給大家介紹了筆者根據多年研發經驗總結出來的編碼規範和 git

簡潔的程式碼

// 需要判斷多個條件,但是事件最終的狀態只有兩種時 // 第一種複雜寫法 狀態 = 條件存在 && (條件 === "A" || 條件 === "B" || 條件 === "C") ? 狀態1 : 狀態2; // 簡單寫法 優點是結構清晰,可讀性高,程式碼易維

從身邊開源開始學習,用過才能理解程式碼

2015年12月20日,雲棲社群上線。2018年12月20日,雲棲社群3歲。 阿里巴巴常說“晴天修屋頂”。 在我們看來,寒冬中,最值得投資的是學習,是增厚的知識儲備。 所以社群特別製作了這個專輯——分享給開發者們20個彌足珍貴的成長感悟,50本書單。 多年以後,再回首2018-19年,留給我們自己的,

雲棲專輯 | 阿里開發者們的第3個感悟:從身邊開源開始學習,用過才能理解程式碼

2015年12月20日,雲棲社群上線。2018年12月20日,雲棲社群3歲。 阿里巴巴常說“晴天修屋頂”。 在我們看來,寒冬中,最值得投資的是學習,是增厚的知識儲備。 所以社群特別製作了這個專輯——分享給開發者們20個彌足珍貴的成長感悟,50本書單。 多年以後,再回首2018-19年,留給我們自

MonoDevelop 3.0——程式碼完成、效能與快速修復建議

近日,MonoDevelop 3.0釋出了,該版本提供了一些新特性,專注於效能、開發者生產力,特別針對C#開發者。 該版本主要的變化在於MonoDevelop的解析器與程式碼完成現在使用了Mono Compiler Service,確保了未來針對Compiler Service的所有改進都會改善這些IDE特

如何程式碼

設計 1、優雅需要付出代價。從短期利益來看,對某個問題提出優雅的解決方法,似乎可能花你更多的時間。但當它終於能夠正確執行並可輕易套用於新案例中,不需要花上數以時計,甚至以天計或以月計的辛苦代價時,你會看得到先前所花功夫的回報(即使沒有人可以衡量這一點)。這不僅給你一個可

只用這 6 個字元,就可以出任意 JavaScript 程式碼

你可能在網上見過有人用 幾個不同的字元寫的各種稀奇古怪的 JavaScript 程式碼,雖然看起來奇怪,但是能正常執行!比如這個: (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]] 你猜執行結果是什麼?你可以

JavaScript的5個技巧幫助你的條件語句

使用JavaScript時,我們會遇到很多的條件語句,這裡有5個技巧能幫助你寫出更好/簡潔的條件語句。 對多個條件使用Array.includes 更少巢狀,儘早return 使用預設的函式引數和解構 支援Map / Object 語法而不是Sw

5個小技巧讓你JavaScript 條件語句

來源:掘金,譯者:Hopsken 連結:https://juejin.im/post/5bb9e3085188255c352d7326 作者:@Jecelyn Yeen 原文:https://scotch.io/tutorials/5-tips-to-write-better-conditi

五個小技巧讓你JavaScript 條件語句

在使用 JavaScript 時,我們常常要寫不少的條件語句。這裡有五個小技巧,可以讓你寫出更乾淨、漂亮的條件語句。 1. 使用 Array.includes 來處理多重條件 舉個栗子: // 條件語句 function test(fruit) { if (

JavaScript封裝自己的一個彈窗,是雙按鈕的,比較簡單一些 ,其中引用了jQuery來的方法,最後暴露出去,有的建議歡迎評論 。。。。

$(function(){ // 設定自執行函式 (function(jQuery){ // 定義建構函式 var Popup = function (title,text,fn) { this.title = title || '

如何的Java程式碼

Maven仍然是編譯,打包,執行測試的標準化工具。還有其它一些選擇,比如Gradle,不過它們的採用程度遠不Maven。如果你之前沒用過Maven,你可以看下這個Maven的使用示例。我喜歡用一個根POM檔案來包含所有的外部依賴。它看起來就像是這樣。這個根POM檔案只有一個外部依賴,不過如果你的產品很大的話,

十個的表達“你的程式碼的很爛的”的方法

如果你有一個同事,他寫的程式與其說是程式碼,不如說更像希臘神話中女妖美杜莎的頭髮,你當然不能熟視無睹,你應該做出一些反應,但你可選的合適的反應方式並沒有多少:自己默默的幫他整理清楚、向上級抱怨、向其他同事背後嘮叨此事、悶在心裡直到憋不住,或者這最大膽的方法:走上去直接對爛程式設計師說他的程式碼很爛。

JavaScript中的該如何[的]做動效

cli cpu get ron 是個 性能 可見 align max 在用js寫動畫的時候,無非使用 setTimeout/setInterval 或者 requestAnimationFrame 來處理動畫(在jquery的代碼裏也是這麽幹的),本文主要為了記錄下兩者的區

JavaScript (三)在HTML頁面上JavaScript程式碼

前面做了那麼多的鋪墊其實就是小試牛刀,看看JavaScript程式碼是什麼樣的。但是我們平時寫前端程式碼的時候又不是在瀏覽器的控制檯上寫的,而是寫在我們的HTML程式碼裡,那應該怎麼去寫呢?下面我們就來使用HBuilder這個軟體寫一下JS的程式碼吧。 首先呢,開啟HBuilder,然後

「譯」JavaScript條件語句的5條守則

原文地址:5 Tips to Write Better Conditionals in JavaScript 原文作者:ecelyn Yeen(@jecelynyeen) 譯文出自:阿里雲翻譯小組 譯文連結:github.com/dawn-teams/… 譯者:眠雲(楊濤

JavaScript條件語句的5條守則

1.多重判斷時使用 Array.includes function test(fruit) { const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries']; if (redFruits.includes(frui