flow-typedで外部ライブラリの型定義を使う

  • 30
    いいね
  • 0
    コメント

flow-typed とは

flowtype で外部ライブラリを使うと欲しくなってくる型定義ファイルを集めたリポジトリになります。TypeScript には DefinitelyTyped という型情報のリポジトリがありますがそれと似たようなものです。

flowtype/flow-typed on GitHub

このリポジトリではそれらの型定義ファイルをプロジェクトで使うための便利な CLI ツールも npm 経由で提供しています。本稿は flow-typed をプロジェクトで使っていくまでの設定やセットアップについて短く解説します。

CLI ツールのインストール

README.md には準備が整ったら npm に公開するよ、と書かれていますが、すでに公開されていて主要な機能は使えているので npm からインストールします(2016-06-12 時点で 2.0.0-beta.5)。

$ npm install -g flow-typed

特にプロジェクトごとにインストールする理由もないのでグローバルインストールしています。このあたりは好みなので好きなようにしていいと思います。

.flowconfig の設定

flowtype では flow-typed に頼らなくとも自分で外部ライブラリの型情報を定義して使うことができます。例えば個人で作ったユーティリティ的なモジュールの型定義ファイルをプロジェクト間で使いまわしたり、とかも可能です。.flowconfig ファイルではどのディレクトリに型定義ファイルが入っているのか指定します。

CLI ツールはそれらの取得や更新を楽に行えるようにするツールなので、マニュアルに書いてあるセットアップ方法に従って flow-typed のデフォルトのディレクトリである flow-typed.flowconfig[libs] セクションに指定していたんですが、 flow 0.24.0 から何も指定しなくても flow-typed ディレクトリだけは特別扱いしてくれるようになりました。というわけで flow-typed のための設定は不要です。

検索

必要なライブラリの型定義ファイルが存在しているか、もし存在している場合はどのバージョンに対応した定義なのかを flowtype のバージョンと共に表示します。ライブラリ名は npm における登録名と同じです。

早速 redux で検索してみると次のように表示されます。

$ flow-typed search redux

Found definitions:
╔═══════════════╤═════════════════╤══════════════╗
║ Name          │ Package Version │ Flow Version ║
╟───────────────┼─────────────────┼──────────────╢
║ redux-actions │ v0.9.x          │ >=v0.23.x    ║
╟───────────────┼─────────────────┼──────────────╢
║ redux         │ v3.x.x          │ >=v0.23.x    ║
╟───────────────┼─────────────────┼──────────────╢
║ redux-form    │ v5.x.x          │ >=v0.22.1    ║
╚═══════════════╧═════════════════╧══════════════╝

flowtype は極力最新のものを使ったほうが良さそうですね。ちなみにもう flow 0.27.0 が出てるっぽいです。Release Noteに記述がないので正式なのかわかりませんが。

インストール

検索をして希望するライブラリの適切なバージョンの型定義があったらそれをインストールして使える状態にします。

$ flow-typed install -f v0.26.0 redux@3.0.0
 * found 1 matching libdefs.
'redux_v3.x.x.js' installed at /path/to/project/flow-typed/npm/redux_v3.x.x.js.

バージョン指定は redux@3 とか redux@3.x みたいに 省略できませんでした。そしてどのバージョンの flowtype に対応した型定義ファイルを取得したいのか -f v0.26.0 のように指定する必要があります。これは特別なことがない限り今使っている flowtype のバージョンを指定すればよさそうです。flow-typed install -f v$(flow version) redux@3.0.0 みたいにできたらよかったんですけど、 flow コマンドにはバージョン番号だけ表示するオプションはありませんでした。個人的には何も指定しなかったときは今使ってる flow のバージョンか、自動的に最新バージョンを指定してくれてもいいんじゃないかなぁって思います。

コマンドを実行するとディレクトリが掘られて npm というサブディレクトリの中にインポートされます。名前空間があるということは社内ライブラリの型定義ファイルを独自にホストすることもできそうですね。

型定義をコードで使う

さて、無事 flow-typed ディレクトリに型定義ファイルをインストールできたと思いますが、このまま flow check を実行してもほとんど意味がありません。というのも、インストールした型情報はグローバルに展開されるものではなく、利用したいファイルごとに明示的に import しないと使えないものだからです。このセクションでは flow-typed でインストールした型定義の使い方について解説します。

運が良い場合

使用しているライブラリによっては export している実体の名前と型の名前が一致していて何もしなくてもエラーチェックをしてくれる場合もあります。例えば redux-actions では createAction という関数を export していますが、flow-typed が提供する redux-actions の型定義も同様に createAction という名前で定義しています。

このようなケースでは redux-actions を普通に import するだけで flowtype は自動的に redux-actions の型定義ファイルから型情報を持ってきてチェックしてくれます。イメージ的には型付きで import される感じですね。

actions.js
// @flow
import { createAction } from 'redux-actions';

export const IPC_REQUEST_CHECK: string = 'IPC_REQUEST_CHECK';
export const ipcRequestCheck = createAction(IPC_REQUEST_CHECK);

例えば createAction を呼び出すときに第1引数として文字列以外を渡すとエラーになります。

actions.js
export const ipcRequestCheck = createAction(10);
$ flow check
src/renderer/actions.js:7
  7: export const ipcRequestCheck = createAction(10);
                                    ^^^^^^^^^^^^^^^^ function call
  7: export const ipcRequestCheck = createAction(10);
                                                 ^^ number. This type is incompatible with
  5:   declare function createAction(type: string, payloadCreator?: Function, metaCreator?: Function): Function;
                                           ^^^^^^ string. See lib: flow-typed/npm/redux-actions_v0.9.x.js:5


Found 1 error

便利ですね。

大抵の場合

実際には export される名前と型の名前が一致するというのはそれほど多くなくて、どちらかというと明示的に型情報を import しています。その場合は type というキーワードを使って型情報のみを import します。次のコードでは通常の redux の import の次の行で StoreMiddleware という名前の型を import しています。

store.js
import { createStore, applyMiddleware } from 'redux';
import type { Store, Middleware } from 'redux';

export default function configureStore(initialState): Store {
  const sagaMiddleware: Middleware = createSagaMiddleware();
  const store: Store = createStore(
    reducer,
    initialState,
    applyMiddleware(
      sagaMiddleware, ipc, logger()
    )
  );
  sagaMiddleware.run(rootSaga);
  return store;
};

とはいえ、このコード中でも createStore とか applyMiddleware は通常の redux の import によって型情報付きになっているので変なものを渡すとエラーになります。

問題点

対応ライブラリが少ない

README にも書いてある 「The flow-typed repo is a collection of high-quality library definitions」 によって21個もの Pull Request が取り込まれないまま残っています。ほとんどがテストがないという理由です。PR する際のベストプラクティスについて別途ドキュメントも用意されています。

あとは大きなライブラリでも自分が使う所だけ定義すればいい、と考えると全体をメンテナンスするモチベーションが湧かないのかもしれません。頑張ってコントリビュートしていきたいです。

最低限の機能のみ

理想的には package.json にもとづいて必要な型定義ファイルをインストールしたりしたいですよね。それっぽい機能は準備中のようなので期待です。まぁ愚痴ってる暇があったら何かしらの手段でコントリビュートしろって話です。

まとめ

あまり flowtype 自体の紹介にはなっていませんが、flow-typed をはじめ徐々にエコシステムが整い始めています。TypeScript は便利そうだけど既存のプロジェクトには簡単に導入できないなって人には、段階的にチェック範囲を広げていける flowtype はハードルが低いんじゃないでしょうか。CLI ツールは基本的な機能は使えるのと、使用しているすべての外部ライブラリの型定義ファイルがなくても flowtype を使いはじめることができるのでぜひ試してみてください。

最後になりましたが、flowtype を使うにあたって変なつまづき方をしていたところを @joe-re さんに助けていただきました。ありがとうございます!

Redux で flowtype を使う実例が載っているこちらの発表はとても参考になりました。
発表資料: https://speakerdeck.com/joere/flowtype-with-flow
サンプルコード:https://github.com/joe-re/pomodo-desuka