使用 Flow 写更好的 JavaScript 代码

lichenxi12 7年前
   <p>你是不是常常发现自己在跟踪代码中的一个 bug,最后发现的错误只是某些本应该可以避免的简单问题呢? 可能你只是按照错误的顺序传递了参数,或者也许是你在尝试传递一个字符串而不是一个数字?JavaScript 的弱类型系统,以及试着将变量在不同类型间强制转换的意图可能就是一整类 bug 的来源,而这些 bug 并不会存在于静态类型语言之中。</p>    <p>Flow 是一个用于 JavaScript 的静态类型检查器,最早由 非死book 在 2014 年的  Scale 大会 上推出。它的目标是不需要开发人员去频繁的修改实际代码,就能找到 JavaScript 代码中的类型错误,从而大大节省这方面时间与精力的消耗。同时,它也向 JavaScript 增加了额外的语法,以提供给开发者更多的控制能力。</p>    <p>在本文中,我会向你介绍 Flow 以及它的主要特性。我们将介绍如何设置它,如何在代码中添加类型注释,以及如何在运行代码时自动删除这些注释。</p>    <h2>安装</h2>    <p>Flow 目前可运行于 Mac OS X, Linux (64 位) 以及 Windows (64 位) 系统上。安装它最简单的办法就是通过 npm:</p>    <pre>  <code class="language-javascript">npm install --save-dev flow-bin</code></pre>    <p>然后将它添加到工程的 package.json 文件, 就在 scripts 部分下面:</p>    <pre>  <code class="language-javascript">"scripts": {    "flow": "flow"  }</code></pre>    <p>完成了此项工作,我们就做好了继续探索其功能的准备。</p>    <h2>入门</h2>    <p>一个被命名为 .flowconfig 的配置文件必须被放在工程文件的根目录之下。通过运行如下命令,我们可以创建出一个空的配置文件:</p>    <pre>  <code class="language-javascript">npm run flow init</code></pre>    <p>放好了这个配置文件,你就能在终端上执行如下命令,来在工程文件夹或者任意子目录下的代码上运行 ad-hoc 检查了:</p>    <pre>  <code class="language-javascript">npm run flow check</code></pre>    <p>不过,这并非使用 Flow 的最有效方式,因为这样做会导致 Flow 每次都会重新检查整个工程文件结构。所以我们可以使用 Flow 服务器。</p>    <p>Flow 服务器会对文件进行增量检查,意思就是它只会检查被修改了的部分。服务器可以通过在终端上执行 npm run flow 命令来启动。</p>    <p>在你第一次运行该命令的时候,服务器将会启动并显示初始的测试结果。该操作运行很快并且是增量的工作流。每次在你想要知道测试结果的时候,就在终端上运行 flow。完成了编码环节之后,可以使用 npm run flow stop 来终止服务器的运行。</p>    <p>Flow 的类型检查是 opt-in 的。这就意味着你无须一次性完成对所有代码的检查。Flow 只会为你检查那些你想要检查的那些文件,怎么让 Flow 知道你想要检查哪些文件呢?只需要在这些 JavaScript 文件顶部添加 @flow 作为注释就可以了:</p>    <pre>  <code class="language-javascript">/*@flow*/</code></pre>    <p>这有利于将 Flow 集成到一个现有工程中,因为你可以选择你想要检查的文件,然后逐步解决所发现的问题。</p>    <h2>类型推断</h2>    <p>通常,类型检查有以下两种方法:</p>    <ul>     <li> <p>通过注解: 我们指定期望的类型并把它作为代码的一部分,类型检查器会基于这些期望来对代码进行评估。</p> </li>     <li> <p>通过代码推断: 工具聪明到可以通过查看代码被使用的上下文来推断出类型,并基于此对代码进行检查。</p> </li>    </ul>    <p>使用注解的话,我们就得编写一些额外的代码,它们只会在开发期间被用到,最终在浏览器加载的 JavaScript 构建版本中被去掉。这样做会增加一些额外的工作,也就是通过加入额外的类型注解来让代码“听话”。</p>    <p>而在第二个场景中,代码随时“待命”, 故而缩减了程序员的工作量。我们不需要对代码进行修改,因为它会自动地推断出表达式的数据类型。这就是 <strong>类型推断</strong> , 它是 Flow 最重要的特性之一。</p>    <p>为了描述该特性,我们来看看如下示例:</p>    <pre>  <code class="language-javascript">/*@flow*/    function foo(x) {    return x.split(' ');  }    foo(34);</code></pre>    <p>这段代码会在你运行 npm run flow 命令的时候在终端上报错, 因为函数 foo() 需要的是一个字符串作为参数,而我们传入的却是一个数字。</p>    <p>报错提示大概如下:</p>    <pre>  <code class="language-javascript">index.js:4    4:   return x.split(' ');                  ^^^^^ property `split`. Property not found in    4:   return x.split(' ');                ^ Number</code></pre>    <p>它明确说明了错误的位置和发生的原因。当我们将参数从数字修改为字符串,报错就会消失,如下:</p>    <pre>  <code class="language-javascript">/*@flow*/    function foo(x) {    return x.split(' ');  };    foo('Hello World!');</code></pre>    <p>上面的代码就不会报错。在这里我们能发现的就是 Flow 理解了 split() 方法只适用于字符串,所以适合的 x 就必须是一个字符串。</p>    <h3>可为空类型</h3>    <p>Flow 不像其它的类型系统一样对待 null。它不会忽略掉 null,因此它可以防范那些作为其他类型传入的 null 可能会导致应用程序崩溃的问题。</p>    <p>看看如下代码:</p>    <pre>  <code class="language-javascript">/*@flow*/    function stringLength (str) {    return str.length;  }    var length = stringLength(null);</code></pre>    <p>在上述场景中,Flow 会抛出一个错误, 我们就不得不像下面这样对 null 进行单独的处理:</p>    <pre>  <code class="language-javascript">/*@flow*/    function stringLength (str) {    if (str !== null) {      return str.length;    }      return 0;  }    var length = stringLength(null);</code></pre>    <p>为了保证代码在所有的情况下都能正常运行,我们专门针对 null 进行检查。Flow 就会将最后的那行代码当做是有效的代码。</p>    <h2>类型注解</h2>    <p>如我在上面所提到过的,类型推断是 Flow 最好的特性之一,因为无需编写类型注解我们就可以得到有效的反馈。不过,在某些情况下,有必要向代码添加注解以更好地检查和消除不确定性。</p>    <p>看看下面的代码:</p>    <pre>  <code class="language-javascript">/*@flow*/    function foo(x, y){    return x + y;  }    foo('Hello', 42);</code></pre>    <p>Flow 在上面的代码中不会发现任何错误,因为 + (加) 操作可以被用在字符串和数字之上, 而我们并没有将add()的参数指定为数字。</p>    <p>在这种情况下,我们可以使用类型注解来指定想要的行为。Type 注解前缀一个 : (冒号),可以被放在函数参数、返回类型以及变量声明之上。</p>    <p>如果我们向上面的代码添加了类型注解,那么它就将如下所示:</p>    <pre>  <code class="language-javascript">/*@flow*/    function foo(x : number, y : number) : number {    return x + y;  }    foo('Hello', 42);</code></pre>    <p>这段代码会显示一个错误,因为函数希望得到的是一个数字作为参数,而我们提供的却是一个字符串。</p>    <p>显示在终端上的错误信息如下所示:</p>    <pre>  <code class="language-javascript">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</code></pre>    <p>如果我们传入的是一个数字而非“Hello”,就不会报错了。类型注解对在庞大且复杂的 JavaScript 文件中指定想要的行为也同样有用。</p>    <p>基于前面的例子,让我们来看看 Flow 支持的一些其它的类型注解。</p>    <h3>函数</h3>    <pre>  <code class="language-javascript">/*@flow*/    /*--------- Type annotating a function --------*/  function add(x : number, y : number) : number {    return x + y;  }    add(3, 4);</code></pre>    <p>上面的代码展示了一个变量和一个函数的注解。add()函数的参数以及返回值都被期望是数字的。如果传入了任何其它的数据类型, Flow 就会抛出一个错误。</p>    <h3>数组</h3>    <pre>  <code class="language-javascript">/*-------- Type annotating an array ----------*/  var foo : Array<number> = [1,2,3];</code></pre>    <p>数组注解采用 Array<T> 的形式,其中的 T 表示数组中单个元素的数据类型。在上述代码中,foo应该是一个元素为数字的数组。</p>    <h3>类</h3>    <p>如下是类和对象的一个示例模式。唯一需要牢记的一点是我们可以使用 | 符号在两个类型间执行 OR 操作。变量 bar1 被加上了注解,期望的是 Bar 类的模式。</p>    <pre>  <code class="language-javascript">/*-------- 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);</code></pre>    <p> </p>    <p>来自:https://www.oschina.net/translate/writing-better-javascript-with-flow</p>    <p> </p>