型なき世界のためのflowtype入門

  • 257
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

http://qiita.com/mizchi/items/3bbb3f466a3b5011b509 で紹介したモダンJSスタックの上に、flowtype を導入して型をボトムアップに追加していくアプローチを紹介します。

なぜflowtypeか、そのゴールは

流行っているライブラリのみを組み合わせて使う場合や、バックエンドとの連携において型が十分に提供される環境なら、正直、flowtypeよりtypescriptでいいと思っています。flowtypeが力を発揮する環境は、既存のJSが大量に存在する環境や、railsなどの動的な型のフレームワーク環境で、静的な定義が抽出できない環境だと思います。

よほど品質が低いライブラリを使わないかぎり、バグはほとんど自分が記述したコードによって発生します。なので、まずは「自分が書いたコードのIFを明確にし、その静的なチェックを行なう」、というのを最初の目標に設定します。

flowの導入と設定

では、実際にflowを導入する方法とその際のアプローチについて解説します。

まずbabelを使っていることを前提に、次のようにシンタックスを追加します。

.babelrc
{
  "presets": ["es2015", "react"],
  "plugins": ["babel-plugin-transform-flow-strip-types"]
}

(npm install -D babel-plugin-transform-flow-strip-types babel-preset-es2015 bable-preset-react も必要)

次に、flow init で設定ファイルを生成し、次のように.flowconfig を設定します。

.flowconfig
[ignore]
.*/node_modules/.*

[include]

[libs]
./src/decls

[options]

flowtypeはignoreで設定しないかぎりnode_modules以下を探索しにいきます。が、大量のnpmモジュールを読み込んだ環境だと間違いなく失敗します。型を想定しない環境に向かって、勝手に型を推論しにいって勝手に失敗するので、自爆しているようなものです。

flowtypeを想定した環境で動かすために動くように修正した fbjs というものがありますが、facebookが使うものに限られてるので、これも無視しましょう。(余談ですが、このfbjsの仕組みは作って自分たちだけは満足してエコシステムを作ろうとしないfacebookの姿勢は、OSSにおいて最悪だと思っています)

じゃあどうするかというと、 libs で指定したディレクトリにそのライブラリで使うライブラリの型を書きます。上の定義だと src/decls がそれに相当します。

libsでその環境の型を書く

自分は、最初はできるだけ握りつぶす方針で導入しました。具体的には次のように書きます。

src/decls/externs.js
declare module "xtend" {
  declare var exports: any;
}

flowtype の libs ではmodule を定義することで import 時に解決するオブジェクトの型定義を指定できます。react や browserify 環境の型はデフォルトで読み込まれています。
exports への declare は default の挙動を定義するので、つまりは次のようなコードが動くようになります。

/* @flow */
import xtend from "xtend";
let ab = xtend({a: 1}, {b: 2});

(正確にはxtendはES modules ではなくcommonjsで書かれbabelによってコンパイルされますが、babelのコンパイル前だとxtendモジュールがあるように見える)

あるいは、要求に応じてより厳密な型を定義してもよいでしょう。

src/decls/externs.js
declare module "xtend" {
  declare function exports<A, B>(a: A, b: B): A & B;
}

グローバル変数として露出する変数の型を握りつぶす場合は、次のように書きます。

src/decls/globals.js
declare var $:any; // jQueryの型チェックを握りつぶす例

(externs.jsとglobals.jsは個人的な分類なので、libsで指定した以下ならどこに書いてもいいです)

膨大なライブラリに型をつけても時間が足りないので、必要に応じて、自分が使う箇所で型の恩恵を受けたい箇所に足していくといいのではないでしょうか。

flowの利点

flowtypeは、外部のモジュールを極力使わない、閉じた型の環境なら typescript と同程度か、それ以上に推論器の性能がいい、と個人的に感じています。

自分は「flowは単なる型アノテーションシンタックスの拡張とその静的なチェッカに過ぎない」という点が気に入っています。基本的にランタイムに介入しません。最悪、型定義はドキュメントとしてのみ使う、といった運用も可能なわけです。

最近のTypeScript は動作環境のロックインが多くなりつつあり、またコンパイル後のJSはedgeで動くからこれでいいんだ、というMSの姿勢が若干透けて見えて、ちょっと距離を置きたい気分です。

flow の欠点

残念ながら、flow は typescript の様に型ファイルが多くは提供されていません。とはいえ、自分は試しに導入して気づいたのですが、導入したライブラリのうち、自分が使っているモジュール/インターフェースは本当に一部だったので、必要なものを書き足すだけで自分の「段階的に少しでも型を足す環境を作る」という目的には、十分な機能を提供してくれました。

おまけ: モダンJSスタックのサンプルリポジトリ

https://github.com/mizchi/modern-js-stack-example-with-react

  • babel
  • browserify/watchify
  • flowtype
  • eslint
  • mocha/power-assert
  • circle ci 上でパスする設定
  • react
  • enzyme

というスタックです。