まずこちらの画面を見てください。
スーパーマリオが右にダッシュすると、マリオワールド上のマリオ座標は右に行くはずです。しかし、画面上のマリオの絵の X座標は、どんなにマリオがダッシュしても真ん中のまま。かわりに、背景が逆方向に動きます。このことから、マリオの現在位置と、マリオの絵の X座標 は、別々の問題だということがわかります。
マリオが今どこをダッシュしているか、などといったドメインロジックと、それをどのようにユーザに表示するかのプレゼンテーション(この場合は、マリオの絵とそれを追うカメラ)は分けて考えることがUI設計パターンの基本です。マリオなみの複雑なUIでなくても、これらが疎結合になっていないと、頻繁に起こる見た目の挙動の修正をするときに作業が複雑になります。標準のブラウザAPI(DOM)だけでは、現時点ではこういった仕組みがないため、凝った UI を作る時はフレームワークの利用を検討しましょう。
近年はネイティブアプリなどブラウザ以外の Web サービスのクライアントが重要になってきており、Web サーバーは、ブラウザが直接解釈できる HTML だけでなく、プログラムが解釈しやすいフォーマットでデータを提供できることが求められています。
JavaScript もサーバーから直接データを受け取り、それを利用した優れた UI のアプリケーションを作ることが可能になってきています。http://feedly.com や http://note.mu や AWS のコンソールなど、普段使っている Web アプリケーションの中にも、サーバーサイドの仕事が HTML をレンダリングすることではなく、API ベースになっている例がたくさんあります。
SPA(シングルページアプリケーション)は、HTML のレンダリングを基本的に全てクライアントサイドでやるアプリケーションのことです。
同時に、今までブラウザがやってくれていた仕事、サーバーとの通信やページ遷移、いらなくなったJavascriptオブジェクトやイベントの破棄などが、プログラマの責任になってきます。その辺りの、アプリケーションロジックよりやや低レイヤーな仕事は、フレームワークに任せると便利です。
フロントエンドは、サーバサイドと比べてプロダクトごとに作りたい内容にバラつきがあります。また、ブラウザの進化や流行の変化のスピードも速く、フレームワーク同士も相互に影響し合っているという状況なので、フレームワークの機能を比較するだけではなく、なぜそのアーキテクチャが良いのかを見ておくと、違いを比較しやすいです。
ドメインロジックとプレゼンテーションを分離する設計パターンのうち、古典的なものが Model-View-Controller 略して MVC パターンです。
ドメインロジックを担当するのが M、プレゼンテーションを担当するのが V になります。V の役目は、M の内容をユーザーにわかるように表示すること「だけ」です。V は M のようすを常に監視していて、M の変更を検知したときにだけ重い腰を上げて画面を書き換えます。デザインパターンで言うと V は M のオブザーバーで、大抵はイベントハンドラが間にいます。
C(Controller)は、本来はユーザからの入力を受けつけ、M を変更する役割ですが、ブラウザの DOM はそれ自体が UI 入力を受けつける性質を持っていて、他の GUI 環境と全く同じ条件ではないので、このあたりは実装によって変形が見られておもしろいです。
Backbone.js は、クライアントサイドのルーター、サーバー通信の抽象化など、SPA に必要な機能も揃っており、早くから流行していました。V をどのようにレンダリングするかについてほとんど何も強制されず、フレームワークとしてはかなり薄い部類です。
Backbone.js は MVC パターンにかなり近いので、イベントを待つオブジェクトが、イベントを発火するオブジェクトのことを知っている必要があります。言い替えると、V は M のことを知っている必要があります。主役になる V と M のうち、入出力が集中しがちな V を肥大化させないため、V と M は 1対多の関係にすることがベストプラクティスになっていきます。(Marionette や Chaplin などのプラグインを使うと V に親子関係を持たせたりとより構造化できる)
結果的に、ページ内に V と M のカップルがたくさんできる形になっていきやすいですが、そのカップルたちの関係をきれいにしようとしているのが 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.lenght <= 0) { viewModel.valid = false }
これで、見た目に関係するコードが完全に消えました。MVVMと言われているフレームワークは、VM と V の同期をHTML テンプレート上で定義するデータバインディングと呼ばれる機能を持っており、V についてはデザイナの領域まで追い出せます。HTML と js オブジェクト、双方向(2-way)でデータバインディング機能を持っていフレームワークはたくさんあり、データバインディングのみを提供するライブラリもたくさん登場しています。
また、データバインディングに関係する機能がブラウザ標準に追加される動きがあります。
AngularJS は、双方向データバインディングを持ったフレームワークの中で人気の存在です。Controller、Service、Directive など複数のコンポーネントが協調して動作し、本体がそれなりに大きいですが、管理画面のような CRUD 操作の多いアプリケーションの場合、アプリケーション層のコード量をかなり減らしてくれます。
本体に組み込まれている DI やモジュールの仕組みなどがとても独特な記法だったりかなり癖がありますが、ユニットテストのやり方などにレールが敷かれていたりするので、JavaScript を愛しているわけではない人々によるプロジェクトではメリットがありそうです。
Vue.js は双方向データバインディングのみを提供してくれるライブラリの中で人気のようです。
Virtual DOM という機能が近頃話題になっています。
ブラウザは、描画エンジンでもあり、かつレイアウトエンジンでもあるので、DOM の操作や、計算が必要なプロパティの参照はコストが大きく、こういった操作は最小限の回数、最小限の範囲に留めるようにしないといけません。この制約を取り払ってくれるのがVirtual DOMです。Javascriptは、DOM ツリーを模した js オブジェクト (= Virtual DOM) を操作し、コストの大きい本物の DOM の更新は Virtual DOM が隠蔽します。このとき、Virtual DOM は差分のみを計算して描画してくれるので、DOM操作のコストを気にすることから解放されます。
DOM 書き換えのコストを気にする必要がなくなると、V と M がお互いにお互いを監視する密接な関係にする必要性が薄れてきます。
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 は、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全体の再描画が頻繁に発生してしまうことが想像されます。