React Native填坑之旅--Flow篇

RZZMad 8年前
   <p>flow不是React Native必会的技能,但是作为正式的产品开发优势很有必要掌握的技能之一。所以,算是RN填坑之旅系列的番外篇。</p>    <p>Flow是一个静态的检查类型检查工具,设计之初的目的就是为了可以发现JavaScript脚本里不容易被发现的错误。在js开发的过程中,总会遇到一些问题。小的还可以,比如用alert或者console等输出一些信息可以debug,并解决。但是如果项目比较大的时候,这些手法只能起到一定的辅助作用。更有甚者,有些问题不运行到那段代码,根本不会发现错误。非死book的兄弟们就是为了解决这个问题,于是开发了flow。</p>    <p>首先,跳转的你的项目目录下。然后就开始正文了。</p>    <h2><strong>安装&配置</strong></h2>    <p>Flow就从安装开始。</p>    <pre>  <code class="language-javascript">npm install --save-dev flow-bin</code></pre>    <p>创建配置文件。</p>    <pre>  <code class="language-javascript">touch .flowconfig</code></pre>    <p>先不管空白的 <em>.flowconfig</em> 配置文件。在 <em>package.json</em> 文件里flow脚本。</p>    <p>your project/package.json</p>    <pre>  <code class="language-javascript">"scripts": {      "flow": "flow; test $? -eq 0 -o $? -eq 2",    },</code></pre>    <p>然后给需要flow检查的文件里加上 //@flow 或者 /*@flow*/ 。然后就可以检查了。(也可以在命令中加上--all, 这样就会检查所有文件)。</p>    <p>在根目录下运行命令:</p>    <pre>  <code class="language-javascript">npm run flow</code></pre>    <h2><strong>用Flow检查</strong></h2>    <p>现在就把flow用起来。Flow绝不是上面几个命令而已,而是一套类型体系。下面通过一个例子来了解一下flow和flow的类型体系。</p>    <pre>  <code class="language-javascript">export default function(state = initialState, action) {    switch (action.type) {      case actionTypes.TRACKS_SET:        return setTracks(state, action);      case actionTypes.TRACK_PLAY:        return setPlay(state, action);    }    return state;  }    function setTracks(state, action) {    const { tracks } = action;    return { ...state, tracks };  }</code></pre>    <p>第一次检查会出很多的错,因为上面的写法没有按照flow的定义添加类型声明。下面来添加类型声明。</p>    <pre>  <code class="language-javascript">// @flow    import * as actionTypes from './actionTypes';    const initialState = {      tracks: [],      activeTrack: null  };    export default function(state = initialState, action) {    switch (action.type) {      case actionTypes.TRACKS_SET:        return setTracks(state, action);      case actionTypes.TRACK_PLAY:        return setPlay(state, action);    }    return state;  }    function setTracks(state, action) {    const { tracks } = action;    return { ...state, tracks };  }    function setPlay(state, action) {    const { track } = action;    return { ...state, activeTrack: track };  }</code></pre>    <p>运行命令check命令之后会显示错误的内容:</p>    <pre>  <code class="language-javascript">test/track.js:10                                                                                           10: export default function(state = initialState, action) {                                                                           ^^^^^ parameter `state`. Missing annotation                                                                                                                                            test/track.js:10                                                                                           10: export default function(state = initialState, action) {                                                                                                 ^^^^^^ parameter `action`. Missing annotation </code></pre>    <p>flow的错误提示我们,需要给出方法的参数类型。</p>    <h3><strong>Flow: Any类型</strong></h3>    <p>修改代码:</p>    <pre>  <code class="language-javascript">export default function(state: any = initialState, action: any) {    switch (action.type) {      case actionTypes.TRACKS_SET:        return setTracks(state, action);      case actionTypes.TRACK_PLAY:        return setPlay(state, action);    }    return state;  }</code></pre>    <p>这样修改之后就没有什么错误提示了。我们给参数指定了 <strong>any</strong> 类型。这个类型是所有类型的父类型,也是所有类型的子类型。所以,任何类型都可以用 <strong>any</strong> 代表了。但是这样并不能发挥类型检查的优势。</p>    <h3><strong>Flow:类型别名</strong></h3>    <p>使用flow的类型别名可以解决上面的问题。输出的默认方法的第一个参数其实是一个State类型的实例。在本例中使用的State是一个对象,其中 tracks 是一个数组, activeTrack 是一个可以为空的对象。为State定义一个类型别名:</p>    <pre>  <code class="language-javascript">type State = {    tracks: Array<any>,    activeTrack: ?any  };    const initialState = {      tracks: [],      activeTrack: null  };</code></pre>    <p>正好之前定义的 initialState 就是 State 类型的一个实例。同理,我们也可以为 initialState 的 activeTrack 定义一个类型。</p>    <pre>  <code class="language-javascript">type Track = {    //这里给出定义  }</code></pre>    <p>然后 State 类型就是这样的了:</p>    <pre>  <code class="language-javascript">type Track = {    //这里给出定义  }    type State = {    tracks: Array<any>,    activeTrack: ?Track  };</code></pre>    <p>注意,这里我们用到了一个特殊的类型: <strong>Maybe Type</strong> (可能类型或者可空类型)。这个类型的定义方式就是在类型的前面放一个问号。</p>    <p>下面也为两个方法 setTracks 和 setPlay 定义返回的类型,并应用到对应的方法上:</p>    <pre>  <code class="language-javascript">// @flow    import * as actionTypes from './actionTypes';    type Track = {    trackName: string  };    type State = {    tracks: Array<any>,    activeTrack: ?Track  };    type SetTrackAction = {    type: string,    tracks: Array<Track>  };    type PlayTrackAction = {    type: string,    track: Track   };    const initialState = {      tracks: [],      activeTrack: null  };    export function setTracks(tracks: Array<Track>): SetTrackAction {    return {      type: actionTypes.TRACKS_SET,      tracks    };  }    export function setPlay(track: Track): PlayTrackAction {    return {      type: actionTypes.TRACK_PLAY,      track: track     };  }</code></pre>    <h3>Flow: Type Union</h3>    <p>SetTrackAction 和 PlayTrackAction 可以使用 <strong>Type Union</strong> 的方式统一起来:</p>    <pre>  <code class="language-javascript">type Action = SetTrackAction | PlayTrackAction;</code></pre>    <p>修改代码:</p>    <pre>  <code class="language-javascript">export function setTracks(tracks: Array<Track>): Action {    return {      type: actionTypes.TRACKS_SET,      tracks    };  }    export function setPlay(track: Track): Action {    return {      type: actionTypes.TRACK_PLAY,      track: track     };  }</code></pre>    <h3><strong>Flow: 模块处理</strong></h3>    <p>这里主要说明一种情况。如果引入的另外一个模块和本模块定义了一个同名的类型别名,但是里面包含的内容不同,那么Flow会检查出来并报错。</p>    <p>比如,现在我们的模块里有了 Track 这个类型,是这样的:</p>    <pre>  <code class="language-javascript">type Track = {    trackCode: string  };</code></pre>    <p>如果在引入的 <em>actionTypes.js</em> 文件中也包含一个 Track 类型,但是定义的有些不同:</p>    <pre>  <code class="language-javascript">type Track = {    trackCode: number  };</code></pre>    <p>两个 Track 的不同就在于 trackCode 的类型,一个是 <strong>string</strong> ,一个是 <strong>number</strong> 。</p>    <p>运行flow之后就会显示出来具体的报错:</p>    <pre>  <code class="language-javascript">test/actionTypes.js:13                                                                                     13:            trackCode: 123                                                                                                       ^^^ number. This type is incompatible with the expected return type of           5:   trackCode: string                                                                                                    ^^^^^^ string  </code></pre>    <h3><strong>Flow: 声明类型</strong></h3>    <p>上面的问题解决起来很简单,把两个类型的定义保持一致就可以。但是,我们不可能在任何一个需要 Track 类型的文件中都定义一个一模一样的类型。</p>    <p>Flow提供了一种特殊的类型声明方式,可以一次声明到处使用。</p>    <p>在 <em>.flowconfig</em> 文件中的[lib]下添加如下内容。如果这个文件为空的话,运行 flow init 命令。</p>    <pre>  <code class="language-javascript">[libs]  decls</code></pre>    <p>在根目录下:</p>    <pre>  <code class="language-javascript">mkdir decls  cd decls  touch flowTypes.js</code></pre>    <p>在文件 <em>flowType.js</em> 中:</p>    <pre>  <code class="language-javascript">declare type Track = {    trackCode: string;  };</code></pre>    <p>把其他的 Track 类型声明全部都删掉,然后运行命令:</p>    <pre>  <code class="language-javascript">npm run flow</code></pre>    <h3><strong>最后</strong></h3>    <p>Flow对React的支持与上文所述的基本上大同小异。</p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007110697</p>    <p> </p>