JavaScript フレームワーク

アジェンダ

どうしてフレームワークを使う?

(1) ドメインロジックとプレゼンテーションの分離

まずこちらの画面を見てください。

スーパーマリオワールド

©任天堂 スーパーマリオワールド

スーパーマリオが右にダッシュすると、マリオワールド上のマリオ座標は右に行くはずです。しかし、画面上のマリオの絵の X座標は、どんなにマリオがダッシュしても真ん中のまま。かわりに、背景が逆方向に動きます。このことから、マリオの現在位置と、マリオの絵の X座標 は、別々の問題だということがわかります。

マリオが今どこをダッシュしているか、などといったドメインロジックと、それをどのようにユーザに表示するかのプレゼンテーション(この場合は、マリオの絵とそれを追うカメラ)とを分けて考えることがUI設計の基本です。マリオなみの複雑なUIでなくても、これらが疎結合になっていないと、頻繁に起こる見た目の挙動の修正作業が複雑になりすぎます。標準のブラウザAPI(DOM)だけでは、現時点ではこういった仕組みがないため、凝った UI を作る時はフレームワークの利用を検討しましょう。

(2) SPA(シングルページアプリケーション)

近年はネイティブアプリなどブラウザ以外の Web サービスのクライアントが重要になってきており、Web サーバーはブラウザが直接解釈できる HTML だけでなく、プログラムが解釈しやすいフォーマットでデータを提供できることが求められています。

JavaScript もサーバーから直接データを受け取り、それを利用した優れた UI のアプリケーションを作ることが可能になってきていて、http://feedly.comhttp://note.mu や AWS のコンソールなど、普段使っている Web アプリケーションの中にも、サーバーサイドの仕事が HTML レンダリングではなく、API ベースになっている例がたくさんあります。

SPA(シングルページアプリケーション)は、HTML のレンダリングを基本的に全てクライアントサイドでやるアプリケーションのことです。

同時に、今までブラウザがやってくれていた仕事、サーバーとの通信やページ遷移、いらなくなったオブジェクトやイベントの破棄などが、プログラマの責任になってくるので、その辺り、アプリケーションロジックよりやや低レイヤーな仕事は、フレームワークに任せると便利です。

流行り廃り

フロントエンドは、サーバサイドと比べてプロダクトごとに作りたい内容にバラつきがあります。また、ブラウザの進化や流行の変化のスピードも速く、フレームワーク同士も相互に影響し合っているという状況なので、機能の比較だけではなく、なぜそのアーキテクチャが良いのかを見ておくと、違いを把握しやすいです。

(1) MVC

ドメインロジックとプレゼンテーションを分離する設計パターンのうち、古典的なものが Model-View-Controller 略して MVC パターンです。

ドメインロジックを担当するのが M、プレゼンテーションを担当するのが V になります。V の役目は、M の内容をユーザーにわかるように表示すること「だけ」です。V は M のようすを常に監視していて、M の変更を検知したときにだけ重い腰を上げて画面を書き換えます。デザインパターンで言うと V は M のオブザーバーで、大抵はイベントハンドラが間にいます。

C(Controller)は、本来はユーザからの入力を受けつけ、M を変更する役割ですが、ブラウザの DOM はそれ自体が UI 入力を受けつける性質を持っているなど、他の GUI 環境と全く同じ条件ではないので、このあたりは実装によって変形が見られておもしろいです。

Backbone.js

Backbone.js は、クライアントサイドのルーター、サーバー通信の抽象化など、SPA に必要な機能も揃っており、早くから流行していました。V をどのようにレンダリングするかについてほとんど何も強制されず、フレームワークとしてはかなり薄い部類です。

Backbone.js は MVC パターンにかなり近いので、イベントを待つオブジェクトが、イベントを発火するオブジェクトのことを知っている必要があります。言い替えると、V は M のことを知っている必要があります。主役になる V と M のうち、入出力が集中しがちな V を肥大化させないため、V と M は 1対多の関係にすることがベストプラクティスになっていきます。(MarionetteChaplin などのプラグインを使うと V に親子関係を持たせたりとより構造化できる)

結果的に、ページ内に V と M のカップルがたくさんできる形になっていきやすいですが、そのカップルたちの関係をきれいにしようとしているのが MVVM です。

(2) MVVM

M のデータをどう V で表現するかは開発者の自由ですが、M が持つデータを HTML に反映させる実装が単純であればあるほど、その部分のコードが冗長に見えてきます。

我々は、残念なことに、ブラウザ上でスーパーマリオを書くことばかりではなかったのです。

MVVM パターンは、ビューの状態を「ビューモデル(VM)」と呼ばれる構造体として表現しておき、VM と V をあらかじめ宣言したやり方で自動的に同期することで、V を記述するコード量を減らします。VM は、ドメインロジックのデータに加えて、ビューの状態を全て持つところが特徴的です。

たとえば、テキストフィールドへの入力が空欄になっているとき、背景を赤くしたいとします。

// DOM 操作の例
if (input.value.length <= 0) {
  input.style.backgroundColor = 'red'
}  

このような「背景を赤くする」といったコードがロジックにまぎれこんでくると、プログラマでないと変更しづらいし、テストも難しくなります。そこで「入力が間違っている」という状態を、プレゼンテーションに依存しないフォーマットで VM が持ちます。

// ビューモデルの疑似コードです
if (model.name.length <= 0) {
  viewModel.valid = false
}

これで、見た目に関係するコードが完全に消えました。MVVMと言われているフレームワークは、VM と V の同期をHTML テンプレート上で定義するデータバインディングと呼ばれる機能を持っており、V についてはデザイナの領域まで追い出せます。HTML と js オブジェクト、お互いがお互いを監視する双方向(2-way)のデータバインディング機能を持っているフレームワークやライブラリがたくさん登場しています。

また、データバインディングに関係する機能がブラウザ標準に追加される動きがあります。

AngularJS

AngularJS は、双方向データバインディングを持ったフレームワークの中で人気の存在です。Controller、Service、Directive など複数のコンポーネントが協調して動作し、本体がそれなりに大きいですが、管理画面のような CRUD 操作の多いアプリケーションの場合、アプリケーション層のコード量をかなり減らしてくれます。

本体に組み込まれている DI やモジュールの仕組みなどがとても独特な記法だったりかなり癖がありますが、ユニットテストのやり方などにレールが敷かれていたりするので、JavaScript を愛しているわけではない人々によるプロジェクトではメリットがありそうです。

Vue.js

Vue.js は双方向データバインディングのみを提供してくれるライブラリの中で人気のようです。

(3) Virtual DOM

Virtual DOM という機能が近頃話題になっています。

ブラウザは、描画エンジンでもあり、かつレイアウトエンジンでもあるので、DOM の更新や、計算が必要なプロパティの参照はコストが大きく、こういった操作は最小限の回数、最小限の範囲に留めないといけません。上述のフレームワークも、基本的にこの制約の上に置かれていました。

Virtual DOMは、この制約を取り払ってくれます。開発者は、DOM ツリーを模した js オブジェクト (= Virtual DOM) を操作し、コストの大きい本物の DOM の更新は Virtual DOM が隠蔽します。このとき、Virtual DOM は差分のみを計算して描画してくれるので、DOM操作のコストを気にすることから解放されます。

DOM 書き換えのコストを気にする必要がなくなると、V と M がお互いにお互いを監視する密接な関係にする必要性が薄れてきます。

React

React は Facebook による Virtual DOM 実装です。

var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});

仮想 DOM の定義をこんなふうにインラインに埋めます。最初見たときは「え…?」と思いましたが、動きのあるアプリケーションは、HTML とインタラクション的な機能が合体したものが V だと考えると、それらの定義が一緒になっている方がシンプルです。Reactだけの特徴ではないですが、独自のUIコンポーネントを定義するという考え方は、ブラウザの新しい仕様の Web Components に似ています。

Flux

Flux は、React.js の Facebook が打ち出した新しいアーキテクチャです。各コンポーネントをあくまでイベントハンドラで疎結合にするところは MVC と同じですが、特徴は、オブジェクト間の通信の方向を一方向に限定していることです。

Views ---> (actions) ----> Dispatcher ---> (registered callback) ---> Stores -------+
Ʌ                                                                                   |
|                                                                                   V
+-- (Controller-Views "change" event handlers) ---- (Stores emit "change" events) --+

Dispatcher がアプリケーションの中心に位置するイベントのハブになっているところや、ドメインロジックのデータを持つ Stores がアプリケーション内のコレクションを管理する Singleton 的な存在になっているところ(iOS の DataSource に似てる)が、V と M のカップルが大量に存在する形とは違っています。

アプリケーション内のできごと全てがイベントとして中央を流れると、イベントが単に画面を書き換える以上のことに使えるので、オブジェクト同士の通信の見通しが良くなります。

(たとえば、マリオがゴールした後、ゴール寸前のようすをリプレイ再生して見せる。という新機能が必要になったとします。マリオの位置や向きの変化が全てイベントになっていれば、それをログに書いておいて上からイベントを発火させるだけでリプレイが実装できます。また、ネットワークを介しての協力プレイを実装したいときは、ネットワークごしにイベントを送り合うことで実現できます)

Flux は Virturl DOM を前提にしたアーキテクチャです。もし Virtual DOM なしでこの設計をやると、V全体の再描画が頻繁に発生してしまうことが想像されます。

どれを使う?