本記事は VirtualDOM Advent Calendar 2014 の13日目の記事です。
VirtualDOM といえば React だ、と言わんばかりの流れですが、本記事では敢えて Ractive.js を取り上げます。Ractive.js が VirtualDOM かって? 確かめてみましょう。
$ curl -s http://cdn.ractivejs.org/latest/ractive.js | grep -i virtualdom | wc -l
307
良し、大丈夫。VirtualDOMです。
Ractive.js とは?
Ractive.js は本家サイトの説明を借りれば、テンプレートドリブンなUIライブラリです。なんのこっちゃわからないでしょうね。
まずVirtualDOMのメリットとは 生のDOMを直接操作しなくても、JavaScriptのオブジェクトだけを操作すれば、上手いこと自動的に書き換えてくれる ことだと言えます。これを実現するにはHTMLのテンプレートに対して、JavaScriptオブジェクト(コンテキスト)をデータバインディングで与えてうんぬん、する必要があります。React と Ractive.js ではこのテンプレートの与え方が大きく異なります。
React ではテンプレートを実現するために、JSXというHTMLとJSが混合したような形式のファイルを作成し、それをJSにコンパイルして用います。いきなりJSを書くこともできますが、なんだかjQueryを使ってDOMを作っているのと対して変わらないコードになってしまいます。
一方 Ractive.js ではどうなるかというと、HTMLをそのままテンプレートとして用いることができます。mustache というテンプレート表記を用いて、HTML内にデータ参照やループや条件分岐さらにイベント発行や関数呼び出しを記述でき、それを Ractive.js へコンテキストと一緒に渡せば、データバインディングからイベント登録など一切合財を面倒みてくれるのです。
とってもとっつきやすいように感じませんか。実際 Ractive.js は従来のHTMLとJSの知識に、mustache ベースのテンプレートの知識を足すだけで、すぐに利用できてしまいます。
いざ Ractive.js へ
では早速 Ractive.js を見てみましょう。よくあるTODOアプリを題材にします。
少し上のアプリを使ってみてください。テキストフィールドに何かタイプして、Addボタンを押すとTODOアイテムが追加され、TODOアイテムの左のDelボタンを押すと、そのアイテムが削除できるでしょう。
完全なソースコードは JSFIDDLE で参照できます。
HTML/テンプレート解説
まずテンプレートを含んだHTMLを見てみましょう。ちょっとシンタックスハイライトが乱れているのは、無視してください。
<div id="todo_app"></div>
<script id='todo_template' type='text/ractive'>
<h4>Todo items</h4>
<ul>
{{#each items:i}}
<li>
<button on-click='delItem:{{i}}'>Del</button>
<span>{{title}}</span>
</li>
{{/each}}
</ul>
<input type='text' value='{{new_title}}' />
<button on-click='addItem:{{new_title}}'>Add</button>
</script>
Ractive.js 特有の書き方になっている箇所を、順番に解説します。
最初の <div id="todo_app"></div>
は、Ractive.js が展開したHTMLが出力される場所に相当します。これはIDが #todo_app
として参照されます。
次の <script id='todo_template' type='text/ractive'> ~ </script>
がテンプレートの本体です。こちらはIDが #todo_template
として参照されます。
テンプレート内の {{#each items:i}} ~ {{/each}}
は繰り返しを意味します。ここではコンテキストとなるオブジェクトの items
プロパティに、配列が入っていることを想定しています。その配列の全要素に対して、each
の内側のテンプレートが展開されます。またその展開時には i
に、展開中の要素のインデックスが格納されます。あ、もちろん :i
は省略も可能です。
on-click='delItem:{{i}}'
はイベントの発行です。このボタンを押すことで delItem
イベントが発行されます。加えて i
を指定することで、イベントの引数に要素番号が付け加えられます。イベントの詳細は JavaScript の解説に続きます。
<span>{{title}}</span>
は値の展開です。each
で処理している要素から title
プロパティが展開されます。実は {{ ~ }}
の中には、単純な演算も記述できるのですが、本記事では解説しません。
<input type='text' value='{{new_title}}' />
はデータバインディングで、このフォームに入力した文字は、コンテキストの new_title
プロパティとして格納されるのです。ちょっとデータバインディングをやったことがあれば、解説をされるまでもなく想像がつきますよね。
あと詳しくは解説しませんが、当然 addItem
イベントも delItem
同様に引数を持たせて、Addボタンに設定してあります。
展開の例
ちょっと解説が単調すぎて疲れていることでしょう。ここで休憩を兼ねて、イメージが湧くように展開の具体例を見てみましょう。JSON形式で記述される、以下の様なデータ(コンテキスト)があったとします。
{
"items": [
{ "title": "foo" },
{ "title": "bar" },
{ "title": "baz" }
]
}
これを先程提示したテンプレートの簡易版である、下記のテンプレートに当てはめたとしましょう。
<ul>
{{#each items:i}}
<li>{{i}} : {{title}}</li>
{{/each}}
</ul>
すると実際に得られるHTMLは次のようになります。
<ul>
<li>0 : foo</li>
<li>1 : bar</li>
<li>2 : baz</li>
</ul>
どうでしょうか。展開の動作イメージが湧いてきたでしょうか。
JavaScript 解説
テンプレート=HTMLの解説は終わりました。いよいよJavaScriptの方を見てみましょう。JavaScriptはこうなっており、やってることは非常に単純です。
var items = [
{ "title": "buy milk" }
];
new Ractive({
el: '#todo_app',
template: '#todo_template',
data: { items: items }
}).on({
addItem: function (e, title) {
items.push({ title: title });
this.set('new_title', '');
},
delItem: function (e, n) {
this.set('new_title', items.splice(n, 1)[0].title);
}
});
まず Ractive オブジェクトを作成します。この時、展開先の要素をel
で、テンプレートをtemplate
で、コンテキストオブジェクトをdata
で、それぞれ与えています。テンプレートとしては、直接HTMLを含んだ文字列を与えることもできますが、ここではDOMのIDを指定しています。
次に作成した Ractive インスタンスの on()
メソッドを呼び、イベントハンドラを登録しています。ここでは addItem
と delItem
、2つのイベントハンドラを記述、登録しています。イベントハンドラの第2引数には、テンプレートのイベント発行で指定した引数が、渡されます。つまり delItem
ならば消すべき要素のインデックス、addItem
ならばユーザがフォームに入力したテキストです。なおイベントハンドラ内の this
は Ractive インスタンスになっています。
双方のイベントハンドラで、TODOアイテムの入った items
と、入力フォームの new_title
プロパティを操作しています。items
は push()
や splice()
で直接操作しているのに対し、new_title
は ractive.set()
を経由して操作していることに注意してください。もちろん new_title
を直接操作することもできますが、その場合には ractive.update()
を明示的に呼ぶ必要があるでしょう。
より実践的な書き方
さて Ractive.js で書かれたTODOアプリはいかがでしたでしょうか。空行を除いて、わずか 30行のコード で、かくもHTMLとJavaScriptが綺麗にその役割を分担できていることに、心を動かされはしなかったでしょうか。
しかし先の書き方には仕事で使うには好ましくない、実践的ではない部分が多々ありました。ということで、それっぽく書き直したTODOアプリが JSFIDDLE にあります。
ちょっと行数は増えますが、テンプレートはほとんど変わっていません。
主な違いは3つで、1つは Ractive
を派生して ToDo
クラスを作成しました。次にイベントの発行は ToDo
クラスのメソッド呼び出しになりました。最後にコンテキストオブジェクトは、ToDo
インスタンスを作成時に渡すようになりました。
これらの変更により ToDo
が再利用可能=コンポーネントになり、データも柔軟に設定できるようになりました。事実 Ractive.js では、このように作成したコンポーネントを独自の要素(タグ)に割り当てて、テンプレート内に記述することもできます(参考)。
おわりに
本記事では、VirtualDOMという日本ではまだ注目されていない分野の、さらにあまり話題になっていない Ractive.js というライブラリを紹介しました。この Ractive.js は、話題になっていないからダメかというとそんなことはまったくなく、むしろなんでこれが話題になってないのかと疑問に思ってしまうほど、良くできた便利なライブラリーです。
そんな Ractive.js の素晴らしさが1人でも多くの人に伝わってくれたら、そんな想いで本記事を書きました。機会があったら是非、使ってみてください。