Backbone.js を試したから Knockout.js にも挑戦
JavaScript のクライアント MVC フレームワークは Backbone.js でいこうと思っていたんですが、Knockout.js が Ver 2.0 でかなり機能追加されて、ちょっと心変わり。もともと Knockout.js のデータバインディング機能に興味を持っていたところに、テンプレート機能が追加されて、試してみたくなりました。
念のため説明すると、Knockout.js は、.NET 開発者にはお馴染みの、データバインディングや MVVM パターンが特徴のフレームワークです。
ちなみに、Backbone.js の記事はこちら。
ToDo リストのサンプルを作ってみた
Backbone.js で作った ToDo リストのサンプルと、同等のものを Knockout.js でも作ってみました。
<!DOCTYPE html> <html> <head> <title>Knockout Todo</title> <meta charset="utf-8"> </head> <body> <h1>Knockout Todo</h1> <!-- タスクリストを表示する要素。 子要素がタスク1個を描画するためのテンプレートになる。 --> <div data-bind="foreach: tasks"> <div class="task"> <!--通常表示するビュー。--> <div data-bind="visible: !editing()"> <input type="checkbox" data-bind="checked: completed" /> <!--完了していないときだけ表示する。--> <span data-bind="if: !completed()"><span data-bind="text: name"></span></span> <!--完了時は打ち消し線を引く。完了していないときは要素を非表示。--> <del data-bind="if: completed()"><span data-bind="text: name"></span></del> <!--click イベントと TaskViewModel#toggleEdit メソッドをバインドする。--> <a href="javascript:void(0);" data-bind="event: { click: toggleEdit }">[edit]</a> </div> <!--編集時に表示するビュー--> <div data-bind="visible: editing()"> <input type="text" data-bind="value: name"/> <input type="button" data-bind="event: { click: toggleEdit }" value="編集終了"/> <input type="button" data-bind="event: { click: destroy }" value="削除"/> </div> </div> </div> <!--form の submit イベントと、appViewModel の addTask メソッドをバインドする--> <form data-bind="event: { submit: addTask }"> <!--keyup が発生するたびに、appViewModel.newTaskName の値を更新する--> <input type="text" data-bind="value: newTaskName, valueUpdate: 'keyup'" /> <!--appViewModel.newTaskName が空のときはボタンを無効にする--> <input type="submit" data-bind="enable: newTaskName().length > 0" value="登録"/> </form> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <script type="text/javascript" src="https://github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js"></script> <script type="text/javascript"> (function() { // タスク var TaskViewModel = function(name) { // タスク名 this.name = ko.observable(name); // 完了かどうか this.completed = ko.observable(false); // 編集中かどうか this.editing = ko.observable(false); // 編集ビューの切り替え this.toggleEdit = function() { this.editing(!this.editing()); }; // 削除要求コールバック this.requestRemove = function(task) { // 何もしない }; // 削除ボタンのイベントハンドラ this.destroy = function() { if (confirm("削除していいですか?")) { this.requestRemove(this); } }; }; // アプリケーション var appViewModel = { // 新しいタスクの名前 newTaskName: ko.observable(""), // 登録されたタスク tasks: ko.observableArray(), // 登録ボタンのハンドラ addTask: function() { var taskName = this.newTaskName(); var newTask = new TaskViewModel(taskName); // タスクを削除するときのコールバックを設定 var self = this; newTask.requestRemove = function(task) { self.tasks.remove(task); }; this.tasks.push(newTask); this.newTaskName(""); } }; // ビューにバインド ko.applyBindings(appViewModel); }()); </script> </body> </html>
そのままコピペで動くと思います。Backbone.js のときと比べて、コード量が激減。
データバインディング超便利
ビューモデルを更新したら自動でビューも更新されます。バインドは双方向なので、ビューに表示しているデータを input や textarea で変更すると、ビューモデルに反映されます。Backbone.js ではわざわざ change イベントを捕まえて更新するコードを書いていたというのに…。データバインディングを JavaScript で見事に実装していてスゴイ。
ビューのデザインがしやすい
ビューモデルとビューのバインディングは data-bind 属性で指定します。foreach によるコレクションの走査も data-bind。Backbone.js みたいに script タグを使って記述する必要なし。Web ブラウザでビューだけ表示すると、それっぽく表示されるので、デザインしやすいです。
Backbone.js のルーターに相当するものが無い
Backbone.js にはルーティング機能があったので、URL とビューをマッピングでき、ブラウザの履歴にも対応できました。しかし Knockout.js には相当するものがありません。ブラウザの履歴には自力で対応するしかなさそう。
Knockout.js にはサーバーと通信する機能がない
Backbone.js だと Model クラスの fetch や save でサーバーと通信できたのに対し、Knockout.js では jQuery.ajax などを使うことになります。割り切っていて、潔いと思えなくもないです。
まとめ
Backbone.js と Knockout.js 両方使ってみましたが、今回みたいに1ページで終わるような小さいアプリケーションなら、Backbone.js よりも Knockout.js の方が生産性高いです。コードの記述量も少なくて済みます。
逆に、複数のビューを切り替える必要があるなら、今のところ手段を提供している Backbone.js の方がいいです。ただ、今後 Knockout.js.js で良い方法が提案されるかもしれません。