Flow-Typedで型定義をまとめて取り込む

Flowはいまのところマジ大変。どうやら一番成長の速い時期にぶつかってしまったらしく、わからないことを調べても皆がバラバラなことを言ってたりして困る。サンプルで数行のお試しコードを書いてる分にはハマりどころも少ないんだけど、ちゃんと出来上がってるアプリケーションに適用するとなると数日の試行ぐらいではまだ怪しげ。

// @flow
import * as React from 'react';
import { reduxForm, Field } from 'redux-form';
import { Card, MenuItem, FlatButton } from 'material-ui';
import { SelectField, TextField } from 'redux-form-material-ui';

import部分だけ抜き出しましたが、いきなりここからつまずきます。冒頭の@flowコメントディレクティブでFlowの影響下になったとたんに、型定義が無いことが原因で from ‘react’ 以外の全てのモジュールで見つからないとエラーが出ます。外部のライブラリだから型定義も外部にあるべきなのだけど、まだ何もしていなかった。

flow-typed install

$ npm install --save-dev flow-typed

# 私はdirenvでパスを通しているので、node_modules/.binの中にあるCLIを直接実行できる
$ flow-typed install
• Found 47 dependencies in package.json to install libdefs for. Searching...
• rebasing flow-typed cache...
• Installing 8 libDefs...
  • axios_v0.16.x.js
    └> ./flow-typed/npm/axios_v0.16.x.js
axios

#(省略)

/Users/masataka_k/Projects/bookreader/flow-typed/npm
  • redux_v3.x.x.js
    └> ./flow-typed/npm/redux_v3.x.x.js
redux
/Users/masataka_k/Projects/bookreader/flow-typed/npm
• Generating stubs for untyped dependencies...
  • eslint-loader@^1.9.0
    └> flow-typed/npm/eslint-loader_vx.x.x.js

#(省略)

  • node-sass@^4.5.3
    └> flow-typed/npm/node-sass_vx.x.x.js

!! No flow@v0.53.1-compatible libdefs found in flow-typed for the above untyped dependencies !!

I\'ve generated `any`-typed stubs for these packages, but consider submitting 
libdefs for them to https://github.com/flowtype/flow-typed/

$ flow-typed create-stub immutable@3.8.1
• Creating 1 stub...
  • immutable@3.8.1
    └> flow-typed/npm/immutable_vx.x.x.js

おもむろにflow-typedをインストールし、flow-typed installを実行します。するとプロジェクトルートにflow-typedフォルダができてそこにpackage.jsonに書かれているインストール済み依存ライブラリの型定義を持ってきてくれます。axion、react-router、redux-actions、jest、react-redux、enzyme、flow-bin、reduxとこれらについてはきちんと持ってきてくれました。他はみつからず、ローカルにスタブを作ってくれます。これは型が全部anyで潰されているなんでも通るもの。つまり型チェックエラーは出ないけどそもそも型チェックをせずに全通しするものです(シェルメッセージ中の「`any`-typed stubs」)。

Reactは初めからFlowにビルトインされていてここでは作られずにいる一方で、ビルトインされてないのにflow-typedがimmutable.jsの対応を作ってくれないので個別にスタブ作りました。flow-typed/npm/immutable_vx.x.x.jsというファイルに出力されたany-typed stubです。

  • package.jsonのdevDependenciesについて一括処理時に処理対象外とする方法が無いっぽい
  • なぜimmutable.jsはFlowとFlow-Typed両方で仲間はずれなのか?
  • redux-formは@nextのv7で自前で型ライブラリを用意しているが取り込み方がわからない

型定義はどこにあるのか?

FlowおよびFlow-Typedのソースツリーにがっつり型定義がホストされている…。このへん未成熟ですね。Flow本体ではReactの型定義のみ持ってる感じですから、まあ、Flowの姿勢は基本的にReactアプリケーション開発のためのものと言っていいのだろうな。

Flowの抑制

初回はまとまってエラーがでるから、まずは抑制しないと仕事がすすまない。eslintでいうところの、eslint-disable/enableコメントディレクティブみたいなやつを設定しました。

[ignore]
.*/node_modules/*
.*/sass/*
.*/static/*
# goのプロジェクトも同居している。以下はgo関連
.*/bin/*
.*/pkg/*
.*/src/*
.*/vendor/*

[include]

[libs]

[lints]

[options]
esproposal.decorators=ignore
suppress_comment= \\(.\\|\n\\)*\\flow-suppress

Redux-Formなどで@デコレータを使ってます(babel-plugin-transform-decorators-legacy)が、これが引っかかるのでoptionsで「esproposal.decorators=ignore」を登録。そして最後の「suppress_comment」によってコードの中に抑制したいところへコメントディレクティブをつけるとピタッと止まる。

    if (module.hot) {
        // flow-suppress acceptがダメ
        module.hot.accept('./AppReducer', () => {
            store.replaceReducer(combineReducers({ app, router, form }));
        });
    }

上記はHMRをしているところのコード部分ですが、このように自分で作った「flow-suppress」コメントディレクティブをエラーでてるところに付けるとOK。

いまのところの成果(?)

うごくものにFlow適用しているので堅牢性云々もないのですが、Reactコンポーネントから'prop-types'が消えた。今にして思うとPropTypesがReactの本体からv15で外にでたのはFlowの静的型チェックに入れ替えるためなのかもしれない。PropTypesは動的な型チェックだからね。アプリ動かさなくてもeslint-plugin-reactで頑張ってはいましたが、Flowの方がPropTypes+eslintより将来性高いとReactコミッタが考えたのかもしれない。

まあ、なにより、Flowが荒削りなのは辛いけど静的型チェックはなんとなく手応えあって楽しい。