1. 什麼是Flow?javascript
Flow 是javascript代碼的靜態類型檢查工具。它是Facebook的開源項目(https://github.com/facebook/flow),Vue.js(v2.6.10的源碼使用了Flow作了靜態類型檢查。所以咱們如今先來了解下Flow的基本知識,有助於咱們分析源碼。
2. 爲何要用Flow?java
javascript是弱類型語言,弱類型體如今代碼中的變量會根據上下文環境自動改變的數據類型。那麼這種弱類型有優勢也有缺點,優勢是咱們容易學習和使用,缺點是:開發者常常由於賦值或傳值致使類型錯誤。形成一些和預期不同的結果。在代碼編譯的時候可能不會報錯,可是在運行階段就可能會出現各類奇怪的bug。所以在大型項目中咱們有必要使用Flow來作代碼靜態類型檢查。node
下面咱們從一個簡單的demo提及。好比以下代碼:git
function add (x) { return x + 10; } var a = add('Hello!'); console.log(a); // 輸出:Hello!10
如上代碼,x這個參數,咱們在add函數聲明的時候,其實咱們但願該參數是一個數字類型,可是在咱們代碼調用的時候則使用了字符串類型。致使最後的結果爲 "Hello!10"; 爲何會出現這種結果呢?那是由於 加號(+)在javascript語言中,它既有做爲數字的加運算符外,還能夠做爲字符串的拼接操做。github
所以爲了解決類型檢查,咱們可使用Flow來解決。下面咱們來介紹下 如何在咱們項目中使用Flow。npm
3. 開始一個新的 Flow 項目編程
首先咱們建立一個名爲 v-project 項目:json
$ mkdir -p v-project
$ cd v-project
接着,添加Flow, 執行以下命令:bootstrap
$ npm install --save-dev flow-bin
如上安裝完成後,咱們須要在要執行靜態檢查文件的根目錄下 執行一下命令:flow init.執行完成後,咱們會發現咱們根目錄下多了一個 .flowconfig 文件。該文件的做用是: 告訴Flow在這個目錄下文件開始檢測。咱們能夠在該 .flowconfig 配置文件內能夠進行一些高級配置,好比說僅包含一些目錄, 或 忽略一些目錄進行檢測等操做。數組
如今在咱們的項目下會有以下目錄結構:
|--- v-project | |--- node_modules | |--- .flowconfig | |--- package.json
package.json 文件基本代碼以下:
{ "name": "v-project", "devDependencies": { "flow-bin": "^0.106.3" } }
如今咱們在 v-project根目錄下新建 index.js 文件,代碼以下:
// @flow var str = "hello world!"; console.log(str);
接着咱們在項目的根目錄下運行以下命令,若是一切正常的話,會提示以下信息:
$ flow check
Found 0 errors
可是若是咱們把代碼改爲以下所示; 它就會報錯了,index.js 代碼改爲以下:
// @flow var str != "hello world!"; console.log(str);
執行結果以下圖所示:
注意第一行,咱們添加了 // @flow, 是用來告訴 Flow,你須要檢查我這個文件。若是不加這個註釋,Flow就不會檢查該文件了。
固然,咱們能夠強制 Flow 來檢測全部的文件,無論文件有沒有 @flow 註釋,咱們只須要在命令行中帶上 --all 參數就好了,以下所示:
$ flow check --all
可是這個命令,咱們通常狀況下仍是須要慎用的,當咱們在一個大型項目中,該項目假如引入了不少第三方庫,那麼檢測器可能會找到不少咱們不想要的錯誤。
注意:flow check 這個命令雖然是可行,但不是最高效的用法,該命令會讓flow每次都在項目下檢查全部文件一遍。
4. 理解類型註釋
Javascript是一種弱類型語言,在語法上沒有規定明確的表示類型,好比以下JS代碼運行是正常的。
function add(num1, num2) { return num1 + num2; } var result = add(1, '2'); console.log(result); // 輸出:12
如上代碼,輸出的 result 的值爲 '12'; 可是有可能這並非咱們想要的,咱們有可能想要兩個數字相加得出結果,可是編寫代碼的時候,一不當心把參數寫成字符串去了。致使預期的結果不同。
Flow 能夠經過靜態分析和類型註釋,來幫咱們解決相似的問題,讓咱們的代碼更加符合預期。
類型註釋通常都是以 : 開頭的,可使用在方法參數中、變量聲明及返回值中,好比使用類型註釋更改上面的代碼以下:
// @flow function add(num1:number, num2:number) :number { return num1 + num2; } var result = add(1, '2');
執行命令後結果以下所示:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:8:21 Cannot call add with '2' bound to num2 because string [1] is incompatible with number [2]. [2] 4│ function add(num1:number, num2:number) :number { 5│ return num1 + num2; 6│ } 7│ [1] 8│ var result = add(1, '2'); 9│ console.log(result); Found 1 error
如上代碼,num1:number 和 num2:number 的含義是:num1 和 num2 傳遞的參數都爲數字類型的,:number {} 中的 :number的含義是:但願返回結果也是數字類型。上面若是咱們把 '2' 改爲 數字 2 就正常了。
類型註釋在大型複雜的項目文件中頗有用,它能保證代碼按照預期進行。
下面咱們來看看Flow能支持的其餘更多類型註釋,分別爲以下:
函數
// @flow function add(num1:number, num2:number) :number { return num1 + num2; } add(1, 2);
數組
// @flow var foo : Array<number> = [1, 2, 3];
如上數組類型註釋的格式是 Array<number>,number的含義表示數組中的每項數據類型都爲 number(數字) 類型。
類
下面是類和對象的註釋模型,在兩個類型以前咱們可使用 或(|) 邏輯,變量foo添加了必須爲Foo類的類型註釋。
// @flow class Foo { x: string; // x 必須爲字符串類型 y: string | number; // y 能夠爲字符串或數字類型 constructor(x, y) { this.x = x; this.y = y; } } // 類實例化 var foo : Foo = new Foo("hello", 112);
對象字面量
對象字面量須要指定對象屬性的類型便可。以下演示:
// @flow class Foo { x: string; // x 必須爲字符串類型 y: string | number; // y 能夠爲字符串或數字類型 constructor(x, y) { this.x = x; this.y = y; } } var obj : {a : string, b : number, c : Array<string>, d : Foo} = { a : "kongzhi", b : 1, c : ["kongzhi111", "kongzhi222"], d : new Foo("hello", 1) }
Null
假如咱們想任意類型 T 能夠爲null或undefined的話,咱們只須要相似以下寫成 ?T 的格式的便可。
// @flow var foo : ?string = null;
如上代碼,foo 能夠爲字符串,也能夠爲null。
5. 理解模塊界限
在跨模塊使用的時候,Flow須要明確註釋,爲了保證Flow在各自模塊內的獨立檢測,提升性能,所以咱們須要在每一個模塊中有本身的Flow.
在咱們的 v-project 項目目錄中新建一個 module.js, 整個目錄結構假如變爲以下:
|--- v-project | |--- node_modules | |--- index.js | |--- module.js | |--- .flowconfig | |--- package.json
module.js 代碼以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } module.exports = module;
index.js 代碼以下:
/* * index.js * @flow */ var module = require('./module'); var result = module(1122);
在命令行中運行發現報錯,以下提示:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:8:21 Cannot call module with 1122 bound to str because number [1] is incompatible with string [2]. index.js 5│ */ 6│ 7│ var module = require('./module'); [1] 8│ var result = module(1122); 9│ module.js [2] 7│ function module(str: string) : number { Found 1 error
若是咱們把 index.js 代碼中的 module(1122); 改爲 module('1122'); 字符串這樣的,再運行下就不會報錯了。
6. 使用Flow檢測第三方庫模塊
大多數javascript應用程序都依賴於第三方庫。若是在咱們的項目代碼中引用外部資源時,咱們要如何使用Flow呢?慶幸的是,咱們不須要修改第三方庫源碼,咱們只須要建立一個庫定義 (libdef). libdef是包含第三方庫聲明的JS文件的簡稱。
下面咱們來演示下這個過程,假如咱們選擇了 lodash 庫。下面咱們的 index.js 代碼中使用了該庫。以下代碼所示:
// @flow import _ from 'lodash';
而後咱們在命令行運行的時候 會報錯以下信息:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:4:15 Cannot resolve module lodash. 1│ 2│ // @flow 3│ 4│ import _ from 'lodash'; 5│ 6│ 7│ Found 1 error
這是由於flow 找不到 lodash 模塊,所以咱們這個時候須要去下載 lodash 的模塊文件,咱們可使用 flow-typed 來管理這些第三方庫的定義文件。
1. flow-typed
flow-typed 倉庫包含了不少流行的第三方庫的定義文件。 flow-typed 能夠看github代碼(https://github.com/flow-typed/flow-typed)
咱們使用npm命令行方式全局安裝下 flow-typed, 以下命令:
npm install -g flow-typed
安裝成功後,咱們須要查找該庫,是否存在咱們的 flow-typed 倉庫中,以下命令查找下:
flow-typed search lodash;
運行命令完成後,咱們就能夠看到有以下版本的了。
Found definitions: ╔═══════════╤═════════════════╤══════════════════════╗ ║ Name │ Package Version │ Flow Version ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash-es │ v4.x.x │ >=v0.104.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash-es │ v4.x.x │ >=v0.63.x <=v0.103.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.47.x <=v0.54.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.38.x <=v0.46.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.55.x <=v0.62.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.63.x <=v0.103.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.104.x ║ ╟───────────┼─────────────────┼──────────────────────╢ ║ lodash │ v4.x.x │ >=v0.28.x <=v0.37.x ║ ╚═══════════╧═════════════════╧══════════════════════╝
如今咱們能夠選擇一個版本進行安裝,咱們須要在咱們的項目根目錄下運行以下命令:
flow-typed install lodash@4.x.x;
文件下載完成後,會自動在咱們的項目根目錄下 新建一個 flow-typed/npm 文件夾,在該文件夾下有一個 lodash_v4.x.x.js文件。
那麼這個時候,咱們再運行 flow check; 命令就不會報錯了。
2. 自定義libdef
若是咱們用的庫在flow-typed倉庫搜索不到怎麼辦?好比我引入了一個在flow-typed管理庫中找不到的庫,好比該庫叫 "kongzhi" 庫(可是在npm包中確實有該庫),該庫下有對外暴露的方法,好比叫 findWhere 這樣的方法,咱們在 index.js 中調用了該方法,而且該庫的假如別名對外叫_; 以下代碼:
// @flow var obj = [ { title: 'kongzhi1111', flag: true }, { title: 'kongzhi2222', flag: false } ]; function test() { return _.findWhere(obj, {flag: true}); }
所以 運行 flow check; 命令後,會報以下錯誤:
$ flow check Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:10:10 Cannot resolve name _. 7│ ]; 8│ 9│ function test() { 10│ return _.findWhere(obj, {flag: true}); 11│ } 12│ 13│ Found 1 error
如上代碼報錯,那是由於Flow不認識全局變量 _. ,要解決這個問題,咱們須要爲咱們的 kongzhi庫建立一個接口文件。
所以咱們須要在咱們項目中根目錄下新建一個叫 "interfaces" 文件夾,在該文件夾下新建一個 'kongzhi.js' 文件,在該文件下代碼以下所示:
declare class Kongzhi { findWhere<T>(list: Array<T>, properties: {}) : T; } declare var _: Kongzhi;
而後咱們須要在咱們的 根目錄中的 .flowconfig 文件中配置 [libs] 爲 interfaces/ 了, 以下所示:
[ignore] [include] [libs] interfaces/ [lints] [options] [strict]
如上,在 .flowconfig 中默認有如上配置項。如上配置後,Flow就會查找 interfaces/目錄下的全部 .js 文件做爲接口定義。
有了該接口文件,咱們在命令中再次運行下 就不會報錯了。以下運行結果:
$ flow check
Found 0 errors
如今整個項目的目錄結構變爲以下:
|--- v-project | |--- flow-typed | | |--- npm | | | |--- lodash_v4.x.x.js | |--- interfaces | | |--- kongzhi.js | |--- node_modules | |--- .flowconfig | |--- index.js | |--- module.js | |--- package.json
更多的自定義 libdef,請查看(https://flow.org/en/docs/libdefs/creation/)。
7. 剔除類型註釋
類型註釋不是咱們JS規範的一部分,所以咱們須要移除它,這裏咱們使用Babel來移除它。
好比咱們的 module.js 中的代碼,以下代碼:
function module(str: string) : number { return str.length; }
str: string 和 : number 它不是咱們的JS規範中的一部分,所以無論咱們在瀏覽器端仍是在nodeJS中運行都會報錯的。
爲了簡單的來測試下,咱們在node.js中運行測試下,以下:
$ node module.js function module(str: string) : number { ^ SyntaxError: Unexpected token : at new Script (vm.js:80:7) at createScript (vm.js:264:10) at Object.runInThisContext (vm.js:316:10) at Module._compile (internal/modules/cjs/loader.js:670:28) at Object.Module._extensions..js (internal/modules/cjs/loader.js:718:10) at Module.load (internal/modules/cjs/loader.js:605:32) at tryModuleLoad (internal/modules/cjs/loader.js:544:12) at Function.Module._load (internal/modules/cjs/loader.js:536:3) at Function.Module.runMain (internal/modules/cjs/loader.js:760:12) at startup (internal/bootstrap/node.js:303:19)
如上能夠看到,會報錯,在編程時,咱們但願使用Flow對類型檢查,可是在代碼運行的時候,咱們須要把全部的類型約束要去掉。所以咱們須要使用Babel這個工具來幫咱們去掉。
所以首先咱們要安裝babel相關的庫,安裝命令以下:
npm install --save-dev babel-cli babel-preset-flow
babel-cli: 只要咱們要安裝babel的話,那麼babel-cli庫都須要安裝的。
babel-preset-flow: 該庫目的是去除類型。
安裝完成後,咱們的 package.json 變成以下:
{ "name": "v-project", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-flow": "^6.23.0", "flow-bin": "^0.106.3" }, "scripts": { "flow": "flow check" } }
爲了項目結構合理及方便,咱們把上面的 index.js 和 module.js 放到 src/js 目錄裏面去,所以目錄結構變成以下:
|--- v-project | |--- flow-typed | | |--- npm | | | |--- lodash_v4.x.x.js | |--- interfaces | | |--- kongzhi.js | |--- node_modules | |--- .flowconfig | |--- src | | |--- js | | | |--- index.js | | | |--- module.js | |--- package.json | |--- .babelrc
src/js/module.js 在剔除以前代碼以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } module.exports = module;
如上安裝完成相應的庫以後,咱們須要在項目的根目錄下新建 .babelrc 文件,添加以下配置:
{ "presets": ["flow"] }
咱們如今在項目的根目錄下運行以下命令,就能夠在項目的根目錄生成 dist/js 文件夾,該文件夾下有index.js和module.js文件,以下命令:
./node_modules/.bin/babel src -d dist
而後咱們查看 dist/js/module.js 代碼變成以下:
/* * module.js * */ function module(str) { return str.length; } module.exports = module;
咱們也能夠在node命令行中測試:$ node dist/js/module.js 後也不會報錯了。
8. Flow類型自動檢測
如上咱們雖然使用了Babel去除了Flow的類型註釋,可是咱們並無對Flow的靜態類型檢測。所以若是咱們想讓babel進行Flow的靜態類型校驗的話,那麼咱們須要手動集成另一個插件--- babel-plugin-typecheck。
想了解更多相關的知識, 能夠看npm庫(https://www.npmjs.com/package/babel-plugin-typecheck)。
接下來咱們須要進行具體的babel集成步驟,所以咱們須要安裝 babel-plugin-typecheck 插件。以下命令:
npm install --save-dev babel-plugin-typecheck
固然咱們要全局安裝下 babel-cli; 以下命令:
npm install -g babel-cli
接下來,咱們須要在咱們的 .babelrc 中添加 typecheck 插件進去,以下所示:
{ "presets": ["flow"], "plugins": ["typecheck"] }
如今咱們就可使用babel來編譯咱們的Flow代碼了。以下命令所示:
$ babel src/ -d dist/ --watch src/js/index.js -> dist/js/index.js src/js/module.js -> dist/js/module.js
如今咱們把 src/js/module.js 中的代碼改爲以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } var str != "hello world!"; console.log(str); module.exports = module;
而後咱們保存後,在命令行中會看到以下報錯信息:
能夠看到,咱們使用babel就能夠完成了校驗和編譯的兩項工做。不再用使用 flow check; 這樣的對全局文件進行搜索並檢測了。
咱們使用了 babel 的 --watch 功能解決了以前 Flow命令不能同時監聽,提示的缺憾了。
babel的缺陷:
可是使用 babel 也有缺陷的,好比咱們如今把 src/js/module.js 代碼改爲以下:
/* * module.js * @flow */ function module(str: string) : number { return str.length; } function foo(x : number) :number { return x * 12; } foo('a'); module.exports = module;
如上代碼,咱們添加了一個foo函數,有一個參數x,咱們但願傳遞的參數爲 number 類型,而且但願返回的值也是 number 類型,可是咱們在 foo('a'); 函數調用的時候,傳遞了一個字符串 'a' 進去,babel 在檢測的時候並無報錯,這或許是它的缺陷。
可是咱們在 flow check; 就會報錯以下:
9. 瞭解 .flowconfig 配置項
咱們在項目中的根目錄運行命令:flow init; 會建立 .flowconfig文件,該文件的做用是告訴Flow在這個目錄下開始檢測。不過 .flowconfig配置項也提供了一些配置選項,告訴Flow哪些文件須要檢測,哪些文件不須要檢測。
.flowconfig 默認有以下配置(咱們講解前面三個比較經常使用的配置項):
[ignore]
[include]
[libs]
[lints]
[options]
[strict]
1. [ignore] 該配置是用來告訴flow哪些文件不須要檢測,默認爲空,全部的文件須要檢測。咱們也可使用正則去匹配路徑,哪些路徑不須要進行檢測。
[ignore]
.*/src/*
如上表示的含義是:在src目錄下的全部文件不須要檢測,所以若是src下有某個js文件是不對的類型,也不會報錯的。
2. [include] 該配置是用來告訴Flow還要檢測哪些文件或者目錄。該配置的每一行表示一個待檢測的路徑,咱們可使用相對於根目錄下的路徑,或者絕對路徑,或支持一個或多個星號通配符。好比以下:
[include] ../xx.js ../xxxDir/ ../xxxDir/*.js
注意:若是 [ignore] 和 [include] 同時存在,而且同時匹配同個路徑,那就看那個配置在後面,那個優先級就更高。
3. [libs]
該配置下通常存放第三方接口文件,當咱們引用第三方庫文件後,咱們須要聲明一個接口文件,咱們能夠放在該目錄下。好比:
[libs]
interfaces/
Flow就會查找 interfaces/目錄下的全部 .js 文件做爲接口文件的定義。
如上就是Flow一些基本的知識瞭解下便可。