Flowは、2014年のScale ConferenceでFacebookがはじめて発表したJavaScript用の静的型チェッカーです。JavaScriptコードの型エラーを見つけるのを目的として考案され、実際のコードを変更する必要があまりなく、プログラマーの労力をほとんど使いません。同時に、JavaScriptに構文を追加し、開発者がより簡単に型を管理できるようにもできます。
この記事では、Flowと主な特徴を紹介します。セットアップ方法、コードに型アノテーションを追加する方法、追加したアノテーションをコードを実行するときに自動的に取り除く方法について説明します。
インストール
現在、FlowはOS X、Linux(64ビット)、Windows(64ビット)で動きます。もっとも簡単なインストール方法はnpmを使う方法です。
npm install --save-dev flow-bin
scriptsセクションで、プロジェクトのpackage.jsonファイルにFlowを加えます。
"scripts": {
"flow": "flow"
}
準備ができたので、Flowの特徴を説明します。
はじめに
プロジェクトフォルダーのルートに.flowconfigという名前のコンフィグレーションファイルが必要です。 次のコマンドを実行して、空のコンフィグファイルを作ります。
npm run flow init
コンフィグファイルができて、ターミナルから次のコマンドを実行すると、とりあえずプロジェクトフォルダーやサブフォルダーにあるコードをチェックできます。
npm run flow check
しかし、この方法は、毎回Flowがプロジェクト全体のファイル構造をチェックするので、あまり効率的な方法ではありません。代わりの方法として、Flowサーバーを使います。
Flowサーバーはファイルの増分チェック、すなわち変更があった部分だけをチェックします。サーバーは、ターミナルからコマンドnpm run flowで実行できます。
最初にこのコマンドを実行したとき、サーバーが起動して初回のテスト結果を表示します。こうしておけば、その後の増分を速くチェックできるワークフローが作れます。テスト結果が知りたいときは、ターミナルからflowを実行します。 コーディングが終わってサーバーを止めるにはnpm run flow stopとします。
Flowの型チェックはオプトインです。つまり、一度にすべてのコードをチェックする必要はないということです。チェックしたいファイルを選べば、あとはFlowがチェックします。ファイルを選ぶには、FlowでチェックしたいJavaScriptファイルの先頭に@flowをコメントとして加えるだけです。
/*@flow*/
この方法は、チェックしたいファイルを1つ1つ選びながらエラーを解決できるので、既存のプロジェクトにFlowを統合するときにとても便利です。
型推論
通常、型チェックは次の2つの方法を使います。
- アノテーションを使う方法:予測する型をコードの一部として指定し、型チェッカーがその予測に基づいてコードを評価する
- コード推論による方法:ツールが変数が使われている文脈から型を賢く推論し、推論に基づいてコードをチェックする
アノテーションを使った方法は、開発中にしか役に立たない追加のコードを書かなければなりません。そのうえ、追加したコードはブラウザーに読み込まれる最終版のJavaScriptから取り除かなければなりません。型アノテーションを加えてコードをチェックするので、前もって余計な作業が少し増えます。
2番目のコード推論による方法は、コードの修正なしでテストの準備ができます。したがって、プログラマーの労力は最小化できます。コードのデータ型を自動で推測するので、コーディングを変える必要はありません。これは型推論と呼ばれ、Flowのもっとも重要な特徴です。
下のコードを例として、この特徴について説明します。
/*@flow*/
function foo(x) {
return x.split(' ');
}
foo(34);
このコードは、npm run flowコマンドを実行するとターミナルでエラーを表示します。なぜならfoo()関数は文字列を引数とするのに、数字を引数として渡してしまったからです。
エラーは次のように表示されます。
index.js:4
4: return x.split(' ');
^^^^^ property `split`. Property not found in
4: return x.split(' ');
^ Number
エラーの場所と原因がはっきり示されています。引数を数字から文字列に変更すると、次のスニペットに示すようにエラーはなくなります。
/*@flow*/
function foo(x) {
return x.split(' ');
};
foo('Hello World!');
先に述べたように、上のコードにはエラーがありません。ここで分かるのは、split()メソッドはstringにのみ使えることをFlowが理解し、xはstringだと推論したということです。
Null許容型
ほかの型システムと比較してFlowはnullを異なった方法で取り扱います。 Flowはnullを無視しないので、別の正当な型の代わりにnullが渡されてアプリケーションをクラッシュさせるエラーを防ぎます。
次のコードを例とします。
/*@flow*/
function stringLength (str) {
return str.length;
}
var length = stringLength(null);
上の場合、Flowはエラーを表示します。解決するには、下に示すようにnullを別に扱わなければなりません。
/*@flow*/
function stringLength (str) {
if (str !== null) {
return str.length;
}
return 0;
}
var length = stringLength(null);
nullのチェック方法を導入して、常にコードが正常に動作するように担保します。 Flowはこの最後のスニペットが有効なコードだと判断します。
型アノテーション
上で述べたように、型推論はFlowのもっとも優れた特徴です。なぜなら、型アノテーションを書く必要なしに有用なフィードバックを得られるからです。しかし、コードにアノテーションを加えると、より詳細なチェックが可能となり、あいまいさを排除できることもあります。
以下のコードで説明します。
/*@flow*/
function foo(x, y){
return x + y;
}
foo('Hello', 42);
Flowは上のコードにエラーがないと判断します。なぜなら+(プラス)演算子は文字列と数字に使用可能で、add()のパラメーターは数字に限るという指定がないからです。
この場合、望ましい動作を指定するのに型アノテーションが使えます。型アノテーションは:(コロン)を接頭辞とし、関数パラメーター、戻り値型、変数宣言に使えます。
上のコードに型アノテーションを加えると下のようになります。
/*@flow*/
function foo(x : number, y : number) : number {
return x + y;
}
foo('Hello', 42);
このコードはエラーを表示します。なぜなら、数字を引数とする関数に文字列を渡しているからです。
ターミナルに表示されたエラーは次のようになります。
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
'Hello'の代わりに数字を渡すとエラーはなくなります。大規模で複雑なJavaScriptファイルでは、望ましい動作を指定するために型アノテーションが便利です。
先の例を念頭に、Flowがサポートするいろいろなアノテーションを説明します。
関数
/*@flow*/
/*--------- Type annotating a function --------*/
function add(x : number, y : number) : number {
return x + y;
}
add(3, 4);
上のコードは変数と関数のアノテーションを示しています。add()関数の引数は、戻り値と同様、数字であると推測されます。それ以外のデータ型を渡すとFlowはエラーを返します。
配列
/*-------- Type annotating an array ----------*/
var foo : Array<number> = [1,2,3];
配列のアノテーションはArray<T>という形式で、Tは配列の独立した要素のデータ型を示します。上のコードではfooは要素が数字でなければならない配列です。
クラス
クラスとオブジェクトのスキーマの例を下に示します。1つだけ気をつけることは、|記号を使って2つの型の間でOR演算ができることです。変数bar1はBarクラスのスキーマに関してアノテートされています。
/*-------- 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);
オブジェクトリテラル
クラスと同じようにオブジェクトリテラルも、オブジェクトのプロパティの型を指定してアノテートできます。
/*--------- Type annonating an object ---------*/
var obj : {a : string, b : number, c: Array<string>, d : Bar} = {
a : "hello",
b : 42,
c : ["hello", "world"],
d : new Bar("hello",3)
}
Null
下に示すようにTの代わりに?Tと書くことで、どの型のTもnull/undefinedを組み込めるようにできます。
/*@flow*/
var foo : ?string = null;
この場合、fooは文字列またはnullとなります。
Flowの型アノテーション方法について、ほんの少しだけ説明しました。これらの基本的な型が使いやすいと感じたら、 FlowのWebサイトにある 型マニュアルの参照をすすめます。
ライブラリーの定義
サードパーティのライブラリーのメソッドを使わなければならないことがよくあります。この場合、Flowはエラーを表示しますが、自分のコードのエラーチェックに集中できなくなるので、望ましくありません。
幸い、これらのエラーを防ぐためにライブラリーのコードを触る必要はありません。そのかわり、ライブラリー定義(libdef)を作ります。 libdefはサードパーティのコードが提供する関数やメソッドの宣言が書かれたJavaScriptファイルのすばらしい呼び名です。
どのようなことかをよく理解するために、例で説明します。
/* @flow */
var users = [
{ name: 'John', designation: 'developer' },
{ name: 'Doe', designation: 'designer' }
];
function getDeveloper() {
return _.findWhere(users, {designation: 'developer'});
}
上のコードは次のエラーを表示します。
interfaces/app.js:9
9: return _.findWhere(users, {designation: 'developer'});
^ identifier `_`. Could not resolve name
エラーはFlowが_変数についてなにも関知していないのが原因で起きます。解決するにはアンダースコアをlibdefで定義しなければなりません。
flow-typedを使う
ありがたいことに、人気のある多くのサードパーティライブラリーのために、libdefファイルを含んだflow-typedと呼ばれるレポジトリがあります。これを使うには、関係する定義をプロジェクトのルートの中にあるflow-typedフォルダーにダウンロードするだけです。
手順をもっと効率化するのに、libdefファイルをダウンロードしてインストールするコマンドラインツールがあります。npm経由でインストールします。
npm install -g flow-typed
インストールしたら、flow-typed installを実行してプロジェクトのpackage.jsonファイルを検査し、見つかった依存関係のあるlibdefをダウンロードします。
自分のlibdefを作る
flow-typedレポジトリに自分が使っているライブラリーのlibdefが見つからない場合、自分で作ることもできます。しょっちゅうすることではないので、記事では詳細に説明しませんが、もし興味があればドキュメントを参照してください。
型アノテーションを取り除く
型アノテーションはJavaScript文法の正当な文法ではないため、ブラウザーで実行する前にコードから取り除かなければなりません。the flow-remove-types toolを使うか、トランスパイルにBabelを使っている場合はBabel presetが使えます。記事では最初の方法についてだけ説明します。
最初に、プロジェクトの依存関係としてflow-remove-typesをインストールする必要があります。
npm install --save-dev flow-remove-types
次に、package.jsonファイルにもう1つscriptエントリーを加えます。
"scripts": {
"flow": "flow",
"build": "flow-remove-types src/ -D dest/",
}
このコマンドはsrcフォルダーのファイルから型アノテーションをすべて除去し、distフォルダーにコンパイルしたバージョンを保存します。コンパイルされたファイルはほかのJavaScriptファイルと同じようにブラウザーにロードできます。
ビルドプロセスの一環としてアノテーションを除去するプラグインがいくつかのモジュールバンドラーのために用意されています。
最後に
この記事ではFlowの型チェックのいろいろな特徴について紹介し、どのようにエラーを発見し、コードの質を向上させるかを説明しました。また、ファイルごとに「オプトイン」し、型推論をすることで簡単に使い始めることができ、コードにアノテーションを加える必要なく有用なフィードバックが得られることを解説しました。
※2017年3月30日:Flowライブラリーの変更を反映して、この記事は更新されました。
(原文:Writing Better JavaScript with Flow)
[翻訳:関 宏也/編集:Livit]