本章的目標是提供一些Flow工具的介紹與使用建議。Flow本質上也只是個檢查工具,它並不會自動修正代碼中的錯誤,也不會強制說你沒按照它的警告消息修正,就不會讓你運行程序。固然,並無要求何時必定要用這類的工具,只是這種做法可讓你的代碼更具強健性與提升閱讀性,也能夠直接避去不少沒必要要的數據類型使用上的問題,這種開發方式目前在許多框架與函數庫項目,或是以JavaScript應用爲主的開發團隊中都已經都是必用工具。javascript
注: 本文內容大部份參考自Flow官網,是以前我我的博客文章 - "Flow靜態數據類型的檢查工具,10分鐘快捷入門"的增修版本。html
注: 本文內容字數過萬,去除代碼也有數千字,筆誤在所不免,有錯再回饋留言吧。vue
"奇異博士"說過「使用警語應該要加註在書的最前面」。因此我把注意項目先加在這裏。java
因爲Flow仍是個年輕的項目,問題仍然不少,功能也沒你想像中完整,用起來有時候會卡頓是正常的,效能仍須改善。之後用戶越來越多就會愈做愈好。react
Windows平臺的支持也是幾個月前(2016.8)時的事,Flow只支持64位元的做業系統,32位元就不能用了。git
若是你是要學或用React或Vue.js等等,Flow是必學的。無論你要用不用,庫源碼裏面都用了。github
Flow是個JavaScript的靜態類型檢查工具,由Facebook出品的開源碼項目,問世只有一年多,是個至關年輕的項目。簡單來講,它是對比TypeScript語言的解決方式。npm
會有這類解決方案,原由是JavaScript是一種弱(動態)數據類型的語言,弱(動態)數據類型表明在代碼中,變量或常量會自動依照賦值變動數據類型,並且類型種類也不多,這是直譯式腳本語言的常見特性,但有多是優勢也是很大的缺點。優勢是容易學習與使用,缺點是像開發者常常會由於賦值或傳值的類型錯誤,形成不如預期的結果。有些時候在使用框架或函數庫時,若是沒有仔細看文件,亦或是文件寫得不清不楚,也容易形成誤用的狀況。json
這個缺點在應用規模化時,會顯得更加嚴重。咱們在開發團隊的協同時,通常都是用詳盡的文字說明,來下降這個問題的發生,但JS語言自己沒法有效阻止這些問題。並且說明文件也須要花時間額外編寫,其餘的開發者閱讀也須要花時間。在現今預先編譯器流行的年代,像TypeScript這樣的強(靜態)類的JavaScript超集語言就開始流行,用嚴格的角度,以JavaScript語言爲基底,來從新打造另外一套具備強(靜態)類型特性的語言,就如同Java或C#這些語言同樣,這也是爲何TypeScript稱本身是企業級的開發JavaScript解決方案。react-native
注: 強(靜態)類型語言,意思是可讓變量或常量在聲明(定義)時,就限制好只能使用哪一種類型,以後在使用時若是發生類型不相符時,就會發出錯誤警告而不能編譯。但不僅這些,語言自己也會拓展了更多的類型與語法。
TypeScript天然有它的市場,但它有一些明顯的問題,首先是JavaScript開發者須要再進一步學習,內容很多,也有必定陡峭的學習曲線,不過這還算小事情。重大的事情是須要把已經在使用的應用代碼,都要整個改用TypeScript代碼語法,才能發揮完整的功用。這對不少已經有內部代碼庫的大型應用開發團隊而言,將會是個重大的決定,由於若是不往全面重構的路走,將沒法發揮強(靜態)類型語言的最大效用。
因此許多現行的開源碼函數庫或框架,並不會直接使用TypeScript做爲代碼的語言,另外一方面固然由於是TypeScript並不是普及到必定程度的語言,社羣上有熱愛的粉絲也有不是那麼支持的反對者。固然,TypeScript也有它的優點,自從TypeScript提出了DefinitelyTyped的解決方式以後,讓現有的函數庫能額外再定義出裏面使用的類型,這也是另外一個能夠與現有框架與庫相整合的方案,這讓許多函數庫與框架都提交定義檔案,提供了另外一種選擇。另外一個優點是,TypeScript也是個活躍的開源碼項目,發展到如今也有一段時間,算是逐漸成熟的項目。它的背後有微軟公司的支持,在最近發佈的知名的、全新打造過的Angular2框架中(由Google主導),也採用了TypeScript做爲基礎的開發語言。
如今,Flow提供了另外一個新的選項,它是一種強(靜態)類型的輔助檢查工具。Flow的功能是讓現有的JavaScript語法能夠事先做類型的聲明(定義),在開發過程當中進行自動檢查,固然在最後編譯時,同樣能夠用babel工具來移除這些標記。
相較於TypeScript是另外從新制定一套語言,最後再通過編譯爲JavaScript代碼來運行。Flow走的則是非強制與非侵入性的路線。Flow的優勢是易學易用,它的學習曲線沒有TypeScript來得高,雖然內容也不少,但大概一天以內學個大概,就能夠漸進式地開始使用。並且由於Flow從頭至尾只是個檢查工具,並非新的程序語言或超集語言,因此它能夠與各類現有的JavaScript代碼兼容,若是你哪天不想用了,就去除掉標記就是回到原來的代碼,沒什麼負擔。固然,Flow的功用可能沒法像TypeScript這麼全面性,也不可能改變要做某些事情的語法結構。
總結來講,這兩種方式的目的是有些類似的,各自有優勢也有不足之處,青菜蘿蔔各有所愛,要選擇哪種方式就看你的選擇。
這種類型不符的狀況在代碼中很是容易發生,例如如下的例子:
function foo(x) { return x + 10 } foo('Hello!')
x
這個傳參,咱們在函數聲明時但願它是個數字類型,但最後使用調用函數時則用了字符串類型。最後的結果會是什麼嗎? "Hello!10",這是由於加號(+)在JavaScript語言中,除了做爲數字的加運算外,也能夠看成字符串的鏈接運算。想固然這並非咱們想要的結果。
聰明如你應該會想要用類型來當傳參的識別名,容易一眼看出傳參要的是什麼類型,像下面這樣:
function foo(number) { return number + 10 }
但若是在複合類型的狀況,例如這個傳參的類型能夠是數字類型也能夠是布爾類型,你又要如何寫得清楚?更不用說若是是個複雜的對象類型時,結構又該如何先肯定好?另外還有函數的返回類型又該如何來寫?
利用Flow類型的定義方式,來解決這個小案例的問題,能夠改寫爲像下面的代碼:
// @flow function foo(x: number): number { return x + 10 } foo('hi')
你有看到在函數的傳參,以及函數的圓括號(())後面的兩個地方,加了: number
標記,這表明這個傳參會限定爲數字類型,而返回值也只容許是數字類型。
當使用非數字類型的值做爲傳入值時,就會出現由Flow工具發出的警告消息,像下面這樣:
message: '[flow] string (This type is incompatible with number See also: function call)'
這消息是說,你這函數的傳參是string(字符串)類型,與你聲明的number(數字)不相符合。
若是是要容許多種類型也是很容易能夠加標記的,假使這個函數可使用布爾與數字類型,但返回能夠是數字或字符串,就像下面這樣修改過:
// @flow function foo(x: number | boolean): number | string { if (typeof x === 'number') { return x + 10 } return 'x is boolean' } foo(1) foo(true) foo(null) // 這一行有類型錯誤消息
由上面這個小例子你能夠想見,若是在多人協同開發某個有規模的JavaScript應用時,這種類型的輸出輸入問題就會很常碰見。若是利用Flow工具的檢查,能夠避免掉許多沒必要要的類型問題。
可能你會認爲Flow工具只能運用在小型代碼中,但實際上Facebook會創造出Flow工具,有很大的緣由是爲了React與React Native。
舉一個我最近正在研究的的函數庫代碼中NavigationExperimental(這網址位置有可能會變,由於是直接連到源碼裏),這裏面就預先聲明瞭全部的對象結構,像下面這樣的代碼:
export type NavigationGestureDirection = 'horizontal' | 'vertical'; export type NavigationRoute = { key: string, title?: string }; export type NavigationState = { index: number, routes: Array<NavigationRoute>, }; // ...
Flow具有有像TypeScript語言中,預先定義對象類型的做用。上面代碼的都是這個組件中預先定義的類型,這些類型能夠再套用到不一樣的代碼文檔之中。
export type NavigationGestureDirection = 'horizontal' | 'vertical';
上面這行相似於列舉(enum)的類型,意思是說要不就是'horizontal'(水平的),要否則就'vertical'(垂直的),就這兩種字符串值可以使用。
export type NavigationRoute = { key: string, title?: string };
這行裏面用了一個問號(?)定義在title
屬性的後面,這表明這屬性是可選的(Optional),不過你可能會有點搞混,由於問號(?)能夠放在兩個位置,見下面的例子:
export type Test = { titleOne?: string, titleTwo: ?string }
titleOne
表明的是屬性爲可自定義的(無關緊要),但必定是字符串類型。titleTwo
表明的是類型可自定義,也就是值的部份除了定義的類型,也能夠是null或undefined,不過這屬性是須要的,並且你必定要給它一個值。好的,這有些太細部了,若是有用到再查手冊文檔就能夠。
export type NavigationState = { index: number, routes: Array<NavigationRoute>, };
上面的代碼能夠看到,只要是聲明過的類型(type),一樣能夠拿來拿在其餘類型中套用,像這裏的Array<NavigationRoute>
,就是使用了上面已聲明的NavigationRoute
類型。它是一個數組,裏面放的成員是NavigationRoute
類型,是個對象的結構。
剛已經有說過Flow工具備很大的緣由是爲了React與React Native所設計,由於Flow自己就內建對PropTypes的檢查功能,也能夠正確檢查JSX語法,在這篇官方文檔中有說明,而這在以後介紹React的文檔的例子中就能夠看到。
Flow目前能夠支持macOS、Linux(64位元)、Windows(64位元),你能夠從如下的四種安裝方式選擇其中一種:
直接從Flow的發佈頁面下載可運行檔案,加到計算機中的PATH(路徑),讓flow
指令能夠在命令列窗口訪問便可。
透過npm安裝便可,能夠安裝在全局(global)或是各別項目中。下面爲安裝在項目中的指令:
npm install --save-dev flow-bin
macOS中可使用homebrew安裝:
brew update brew install flow
透過OCaml OPAM套裝管理程序打包與安裝,請見Flow的Github頁面。
在你的項目根目錄的用命令列工具輸入下面的指令,這將會建立一個.flowconfig
文檔,若是這文檔已經存在就不須要再進行初始化,這個設置檔同樣是能夠加入自定義的設置值,請參考Advanced Configuration這裏的說明,目前有不少項目裏面都已經內附這個設置檔,例如一些React的項目:
flow init
通常都在代碼檔案的最上面一行加入,沒加Flow工具是不會進行檢查的,有兩種格式均可以:
// @flow
或
/* @flow */
目前支持Flow工具插件的代碼編輯工具不少,常見的Atom, Visual Studio Code(VSC), Sublime與WebStorm都有,當有安裝搭配代碼編輯工具的插件時,編輯工具會輔助顯示檢查的訊息。不過有時候會有點卡頓的要等一下,由於檢查速度還不是那麼快。
或是直接用下面的命令列指令來進行檢查:
flow check
在Visual Studio Code中由於它內建TypeScript與JavaScript的檢查功能,若是要使用Flow工具來做類型檢查,須要在用戶設置中,加上下面這行設置值以避免衝突:
"javascript.validate.enable": false
注: 有些腳手架就已經裝好與設置好這個babel拓展插件,你不用再多安裝了。
在開發的最後階段要將本來有使用Flow標記,或是有類型註釋的代碼,進行清除或轉換。轉換的工做要使用babel編譯器,這也是目前較推薦的方式。
使用babel編譯器若是以命令列工具爲主,可使用下面的指令來安裝在全局中:
npm install -g babel-cli
再來加裝額外移除Flow標記的npm套件babel-plugin-transform-flow-strip-types在你的項目中:
npm install --save-dev babel-plugin-transform-flow-strip-types
而後建立一個.babelrc
設置檔案,檔案內容以下:
{ "plugins": [ "transform-flow-strip-types" ] }
完成設置後,以後babel在編譯時就會一併轉換Flow標記。
下面的指令則是直接把src
目錄的檔案編譯到dist
目錄中:
babel src -d dist
固然,babel的使用方式不是隻有上面說的這種命令列指令,你能夠視項目的使用狀況來進行設置。
Flow用起來是的確是簡單,但裏面的內容不少,主要緣由是是要看實際不一樣的使用狀況做搭配。JavaScript裏面的原始數據類型都有支持,而在函數、對象與一些新的ES6中的類,在搭配使用時就會比較複雜,詳細的狀況就請到官網文檔中觀看,如下只能提供一些簡單的介紹說明。
Flow支持原始數據類型,以下面的列表:
boolean
number
string
null
void
其中的void
類型,它就是JS中的undefined
類型。
這裏可能要注意的是,在JS中undefined
與null
的值會相等但類型不一樣,意思是做值相等比較時,像(undefined == null)
時會爲true
,有時候在一些運行期間的檢查時,可能會用值相等比較而不是嚴格的相等比較,來檢查這兩個類型的值。
全部的類型均可以使用垂直線符號(|)做爲聯合使用(也就是 OR 的意思),例如string | number
指的是兩種類型其中一種均可使用,這是一種聯合的類型,稱爲"聯合(Union)類型"。
最特別的是可選的(Optional)類型的設計,可選類型表明這個變量或常量的值有可能不存在,也就是容許它除了是某個類型的值外,也能夠是null
或undefined
值。要使用可選類型,就是在類型名稱定義前加上問號(?),例如?string
這樣,下面是一個簡單的例子:
let bar: ?string = null
字面文字類型指的是以真實值做爲數據類型,可用的值有三種,即數字、字符串或布爾值。字面文字類型搭配聯合的類型能夠做爲列舉(enums)來使用,例如如下的一個撲克牌的類型例子:
type Suit = | "Diamonds" | "Clubs" | "Hearts" | "Spades"; type Rank = | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | "Jack" | "Queen" | "King" | "Ace"; type Card = { suit: Suit, rank: Rank, }
注: type是Flow中定義類型別名(Type Alias)的關鍵字,是一種預先聲明的類型,這些聲明的標記同樣只會在開發階段中使用,最後編譯去除。
類型別名(Type Alias)提供了能夠預先定義與集中代碼中所須要的類型,一個簡單的例子以下:
type T = Array<string> var x: T = [] x["Hi"] = 2 //有Flow警告
類型別名(Type Alias)也能夠用於複雜的應用狀況,詳見Flow官網提供的Type Aliases內容。
在某一些狀況可能不須要定義的太過於嚴格,或是還在開發中正在調試時,有一種做爲漸進的改善代碼的類型。
Flow提供了兩種特殊的類型能夠做爲鬆散的數據類型定義:
any: 至關於不檢查。既是全部類型的超集(supertype),也是全部類型的子集(subtype)
mixed: 相似於any是全部類型的超集(supertype),但不一樣於any的是,它不是全部類型的子集(subtype)
mixed
是一個特別的類型,中文是混合
的意思,mixed
算是any
的"囉嗦"進化類型。mixed
用在函數的輸入(傳參)與輸出(返回)時,會有不同的狀態,例如如下的例子會出現警告:
function foo(x: mixed): string { return x + '10' } foo('Hello!') foo(1)
會出現警告消息以下:
[flow] mixed (Cannot be added to string)
這緣由是雖然輸入時能夠用mixed
,但Flow會認爲函數中x
的值不見得能夠與string
類型做相加,因此會請求你要在函數中的代碼,要加入檢查對傳入類型在運行期間的類型檢查代碼,例如像下面修改過才能過關:
function foo(x: mixed): string { if (typeof x === 'number' || typeof x === 'string') { return x + '10' } throw new Error('Invalid x type') } foo('Hello!') foo(1)
mixed
雖然"囉嗦",但它是用來漸進替換any
使用的,有時候每每開發者健忘或偷懶沒做傳入值在運行期間的類型檢查,結果後面要花更多的時間才能找出錯誤點,這個類型的設計大概是爲了提前預防這樣的狀況。
注: 從上面的例子能夠看到Flow除了對類型會做檢查外,它也會請求對某些類型須要有動態的檢查。在官方的文件能夠參考Dynamic Type Tests這個章節。
數組類型使用的是Array<T>
,例如Array<number>
,會限定數組中的值只能使用數字的數據類型。固然你也能夠加入埀直線(|)來定義容許多種類型,例如Array<number|string>
。
對象類型會比較麻煩,主要緣由是在JavaScript中全部的數據類型大概均可以算是對象,就算是基礎數據類型也有對應的包裝對象,再加上有個異常的null
類型的typeof
返回值也是對象。
對象類型在Flow中的使用,基本上要分做兩大部份來講明。
第一種是單指Object
這個類型,Flow會判斷全部的基礎數據類不是屬於這個類型的,如下的例子所有都會有警告:
// 如下都有Flow警告 (0: Object); ("": Object); (true: Object); (null: Object); (undefined: Object);
其餘的複合式數據類型,除了數組以外,都會認爲是對象類型。以下面的例子:
({foo: "foo"}: Object); (function() {}: Object); (class {}: Object); ([]: Object); // Flow不認爲數組是屬於對象
注意: 上面有兩個特例,
typeof null
與typeof []
都是返回'object'。也就是說在JS的標準定義中,null
與數組
用typeof檢測都會返回對象類型。因此,Flow工具的檢查會與JS預設並不相同,這一點要注意。注:
typeof
在Flow中有一些另外的用途,詳見Typeof的說明。
第二種方式是要定義出完整的對象的字面文字結構,像{ x1: T1; x2: T2; x3: T3;}
的語法,用這個結構來檢查,如下爲例子:
let object: {foo: string, bar: number} = {foo: "foo", bar: 0}; object.foo = 111; //Flow警告 object.bar = '111'; //Flow警告
上面已經有看到,函數也屬於對象(Object)類型,固然也有本身的Function
類型,函數的類型也能夠從兩大部份來看。
第一是單指Function
這個類型,能夠用來定義變量或常量的類型。以下面的代碼例子:
var anyFunction: Function = () => {};
第二指的是函數中的用法,上面已經有看到函數的輸出(返回值)與輸入(傳參)的用法例子。例如如下的例子:
function foo(x: number): number { return x + 10; }
由於函數有不少種不一樣的使用狀況,實際上可能會複雜不少,Flow工具能夠支持目前最新的arrow functions、async functions與generator functions,詳見官方的這篇Functions的說明。
類是ES6(ES2015)中新式的特性,類目前仍然只是原型的語法糖,類自己也屬於一種對象(Object)類型。類的使用狀況也可能會複雜,尤爲是涉及多型與實例的狀況,詳見Flow網站提供的Classes內容。
Flow在最近的博客中說明引入了flow-typed的函數庫定義檔("libdefs"),在這個Github存儲庫中將統一存放全部來自社羣提供的函數庫定義檔案。這是一種可讓現有的函數庫與框架,預先寫出裏面使用的類型定義。讓項目裏面有使用Flow工具與這些函數庫,就能夠直接使用這些定義檔,以此結合現有的函數庫與框架來使用。這個做法是參考TypeScript的DefinitelyTyped方式。由於這仍是很新的消息(2016.10),目前加入的函數庫尚未太多,不過React周邊的一些函數庫或組件都已經開始加入,其餘經常使用的像underscore、backbone或lodash也已經有人在提交或維護。
Flow另外一個發展會是在開發工具的自動完成功能的改進,由於若是已經能在撰寫代碼時,就知道變量或常量的類型(靜態類型),那麼在自動完成功能中就能夠更準確地給出可用的屬性或方法。這一個功能在Facebook自家的Nuclide開發工具的Flow說明頁中就有看到。Nuclide是基於Atom開發工具之上的工具,計算機硬件若是不夠力是跑不動的,並且它穩定性與運行速度都還須要再努力。這大概是將來可見到的一些新趨向。
本文簡單的說明了Flow工具的功能介紹,以及其中的一些簡要的內容等等。相信看事後你已經對這個Flow工具備一些認識,以我我的學過TypeScript的經驗,相較於TypeScript的學習曲線,Flow大概是等於不用學。Flow雖然是一個很新的工具,但至關的有用,建議每一個JavaScript開發者均可以試試,一開始不用學太多,大概這篇文檔看完就能夠開始用了。複雜的地方就再查找官方的文件便可。
對於每一個正在使用JS開發稍具規模化的應用,或是開發開源碼的函數庫或框架的團隊來講,讓JS具備靜態類型特性,是一個很重要並且必要的決定。以個人觀察,在網絡上一直有不少的超集語言(例如TypeScript)的愛好者,會提出要全面改用TypeScript(或其餘超集語言)的聲音,例如Vue.js在很早以前就有討論是否是要全面採用TypeScript的聲音。後來Vue.js只有提交TypeScript的DefinitelyTyped文檔,但在2.0中則採行了Flow工具。在這篇Vue做者於知乎上發表的: Vue 2.0 爲何選用 Flow 進行靜態代碼檢查而不是直接使用 TypeScript?的內容中,你能夠看到爲什麼選擇Flow的理由,這可能也是整個開發團隊所認同的最後結果。做者回答的文中能夠總結下面這句話:
所有換 TS(TypeScript) 成本太高,短時間內並不現實。 相比之下 Flow 對於已有的 ES2015 代碼的遷入/遷出成本都很是低 … 萬一哪天不想用 Flow 了,轉一下,就獲得符合規範的 ES。
總之,Flow提供了另外一個選擇,要用什麼工具就看聰明的你如何選擇了。