本章的目标是提供一些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提供了另外一个选择,要用什么工具就看聪明的你如何选择了。