2012/10/05更新

[JavaScript] JSでMVCモデルで大規模開発ができるbackbome.jsを入門してみた

このエントリーをはてなブックマークに追加      

こんにちは、最近JS実装が盛んな@yoheiMuneです。
今日は、JavaScriptを用いた大規模開発で使われるらしいbackbone.jsに入門したので、その内容をブログに残したいと思います。
なお、初心者の記述のため誤記などあるかもしれません。ご指摘頂けると幸いです。

画像



Backbone.jsとは

Backbone.jsとは、JavaScriptでMVC(Model + View + Controller)のモデルでプログラミングする仕組みを提供してくれるライブラリです。
ただ、Backbone.js自体のMVCはModel + View + Collectionと、Cの部分が違うのですが、使い方を見るとMVC(Controllerのほう)で実装できると感じました。

Backbone.jsで利用する代表的な仕組みは、以下となります。
View
DOMの操作やユーザーからのイベントへの対応を行う。
通常のMVC(Model, View, Controller)のViewとControllerを担当する。
Model
データの保持とデータの永続化(サーバー通信、localStorageなど)を行う。
MVCモデルのModelを担当する。
Collection
Modelを複数保持するための仕組み。配列を高機能にした感じ。

基本的には、ViewとModelを用いてMVC(Model+View+Controller)のアーキテクチャで実装してくのが、基本的な流れとなるようです。

上記以外にもRouterとHistoryという機能もあります。
URL変更時に発生するpopstateやhashcangeイベントを監視してルーチン起動を行ってくれるらしいのですが、 まだあまり詳しくないので、説明は割愛させて頂きます。でも超便利そうな機能。。



利用するための準備

githubからソースをダウンロードして読み込むことで利用できます。
backbone.jsはunderscore.jsに依存しているので、underscore.js(ダウンロードしたフォルダのtestディレクトリとかに入ってるはず)も一緒に読み込みます。
また、jQueryかzepto.jsは、bacobone.js利用時にあるとすごく便利なので、どちらかは読み込むようにします。
以下のような読み込み順でHTMLを記述します。
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="backbone.js"></script>
<script type="text/javascript" src="main.js"></script>
最後のmain.jsが、ページ固有のJSファイルです。ここにbackbone.jsを用いた実装を書いていきます。



Viewを用いた実装

Viewでは、DOMの操作(View)やイベントの受付(Controller)の実装を行います。
データの永続化を行わない、単純なデータ構造を扱う(単一文字列のみ)とかなら、ModelやCollectionは無くても、これだけで実装すればいいかも。
var MyView = Backbone.View.extend({

  // 操作対象やイベント受付を行うDOMエレメントを指定する。
  // ここに指定したHTML要素とその子孫が処理対象となる。
  // Viewのインスタンス作成時の引数でも指定することが可能。
  el : "body",

  // 初期処理はここに書く
  initialize: function(options) { /*必要ならば書く */},

  // Listenするイベントを登録する。(Controllerの部分)
  events : {
    "click" : "action1",   // el以下で発生する全てのクリックイベントに反応する。
    "click .do" : "action2",  // el以下の.doクラスが付与された要素のクリックイベントに反応する。
  },

  // イベントに反応する処理を記述する
  action1 : function(e){/*具体的な処理を書く*/},
  action2 : function(e){/*具体的な処理を書く*/}

  // 描画処理を行う際の関数を定義する。(Viewの部分)
  render : function() {
    // 以下の例では、el要素の中にHTMLを追加する。
    $(this.el).append("added text");
  }

});

  // Viewのインスタンスを作成する。
  var myView  = new MyView();
  var myView2 = new MyView({el: $(".target")});  // 引数で対象を指定することも可能。


もっと詳しい解説は、以下ページが良いかと思います。
http://qiita.com/items/8ee2ed1949cd7c337f06



Model

Modelは、データ構造を規定したり、Modelが保持する値の返却・更新をしたり、データの永続化などを行います。
var Task = Backbone.Model.extend({

  // 初期値として持たせたいものを定義する。
  defaults: {
    createDate : new Date(),
    title : "dummy title"
  },

  // インスタンス生成時の処理を記述する。
  // attrで渡した値は、自動的にTaskのフィールドに保持される。
  initialize: function(attr, options) {},


  // saveやfetchやdestroyなどのデータ取得、データ永続化の際にリクエストするURLのベース
  // urlRoot + id(例:/foo/bar/12)がリクエスト先になる。RESTfulですね。
  urlRoot : "/foo/bar/",

  // setやsaveメソッドで呼び出されるバリデーション(slient:falseならこれは実行されない)。
  // 返り値があればerrorイベント発生するので、bindなどで処理する。
  validate: function(attrs) {
    if (attar.title.length === 0) {
      return "タイトルを入力してください";
    }
  },

  // 何か独自の振る舞いをしたい場合には、ここに記述すれば良いのかなぁ。Modelならきっと。
  sayHello : function() {
    alert("hello!! I am " + this.title + " Model");
  }
});

// Modelのインスタンスを生成する。
var attr = {id : 1, title: "sampleTitle", limit: "today"};
var options = {};
var task = new Model(attar, options);

// Modelの値の受け渡しは、set、get、などで行うことができる。
var title = task.get("title");
task.set({limit : "tomorrow"});

// 値の変更時に、イベントを発生させることもできる。
task.bind("change", function(){console.log("change");});
task.bind("change:title, function(){console.log("change:title");});
task.set({limit : "the day after tomorrow"});  // changeとchange:titleが出力される。

// データを永続化する場合はこれを実行する。
// LocalStorageとかへ保存するなら、Backbone.sync?を変更する。
// LocalStorage保存用のBackbone.jsの拡張?もあるようです。
task.save();

// サーバーからデータを取得する場合はこれを実行する。
// localStorageから取得の場合は、オーバーライドする必要あるかも。
// サーバーからかえってきたJSONを、同一プロパティ名に代入してくれる。
task.fetch();
Modelに関する更なる詳細は、以下ページを参照ください。
http://qiita.com/items/5acef8dd49f67fd7813c



Collection

Collectionは、複数のModelを扱う仕組みを提供してくれる。
// Collectionクラスの拡張時に、どのModelのCollectionかを指定する。
// 指定することで、Collectionに値を追加する際に、Modelの中身(raw Data)を渡して追加することができる。
var TaskList = Backbone.Collection.extend({
  model: Task
});

// Collectionのインスタンスを作成する。
var taskList = new TaskList();
// インスタンス生成時に、要素を指定しておくことも可能。
// var taskList = new TaskList([{id : 1, title: "sampleTitle", limit: "today"}]);

// 値を追加したり、削除したり、取得したり。
taskList.add(Taskクラスのインスタンス);

// 値を削除する。
taskList.remove(Taskクラスのインスタンス);

// 値を取得する。
taskList.get(Taskクラスのインスタンスに割り当てたID);

// 値の走査をする関数が色々と。
// each, map, filterとかとか
Collectionに関する更なる詳細は、以下を参照ください。
http://qiita.com/items/c19d2fff0dc23e4015ff



ViewとModelの連携

以上のところで、Backbone.jsの各要素の説明が終わりです。
学んだ感じ、MVC用の仕組み(render, get, set, saveなど)が用意されており、 それに従ってプログラミングすると、MVCらしく書けるんだなぁと感じました。

ViewとModelの連携ですが、Viewにモデルを保持させることで、連携することが可能です。
以下は連携例となります。
// Modelのインスタンスを作成
var task = new Task();

// Viewインスタンス作成時に、Modelを渡す。
// これは外部からModelをインジェクションする方法。他にも事前にView内部で定義することも可能。
var myView = new myView({model: task});
これでView内部でModelを使えるようになります。
続いてView内部でModelを使う方法のサンプルです。
// Viewの定義のところで抜粋
var MyView = Backbone.View.extend({

  // 初期処理で、Modelが発するイベントに対して、Viewが反応するように設定します。
  // ちまたでいうオブザーバーパターンでの実装。
  initialize : function() {
  
    // Modelのsetメソッドでtitleの内容が変更された際に、Viewのrenderを呼び出し、表示を更新する。
    this.model.bind("change:title", this.render());

    // Modelのvalidateでエラーがあった場合に、Viewでエラーを表示する。
    this.model.bind("error", this.showError());
  },

  // 描画する際には、Modelから値を取得してHTMLに反映させると行ったことをする。
  render : function() {

    var title = this.model.get("title");
    this.$el.append("" + title + "");
  },

  // validationエラー時に、画面にエラー内容を表示する。
  showAlert : function(model, error) {
    alert(error);
  },

  // Listenするイベントを登録する。
  events : {
    "click" : "action1"
  },

  // ユーザーからのアクションを受けて、Modelに値を保存する。
  action1 : function(e) {
    this.model.setLimit(e.currentTarget.value);
  },

  /* その他のViewの実装は省略 */

});

上記の実装例の感じで、 ・Modelのイベント発生時に、Viewの指定したイベントを呼び出してもらう
・Modelへの値の設定/値の取得を行う
といったことを行うことで、ViewとModelを連携させることが出来ます。



Backbone.jsを入門して感じたこと

Backbone.jsのサンプル実装したり、ソースコードを読んだりして感じたことを書きたいと思います。
View&ControllerとModelを分離した設計が簡単にできるのは良いこと。
責務の分離を意図した設計ができる点は良い。でもModelの責務分担は、設計の善し悪しが出やすそう。
他のModelで利用する実装内容を使いたいという場合に、処理の切り出しや抽象化といったリファクタリングもできる。ユーザーが拡張したViewのextendもunderscore.jsのおかげで可能のため。
相互参照が怖い気がする。
ViewとModel、ModelとCollectionでそれぞれ相互参照しているので、メモリリークが心配。内部では、不要になったインスタンスの解放が正しく出来てるのか調べてみたい。
unbindやunsetメソッドの呼び出し、delete文の発行などが必要か?
RouterとHistoryという機能が便利そう。
これから学んでみたいと思います。



Backbone.jsの入門で役立ったサイト

理解する上で、以下のサイトが大変助かりました。
- http://qiita.com/items/16b799d0ec0a0ae3f78e (Backbone.js入門第8回に分けて、日本語で詳しく説明)
- http://backbonejs.org/(Backbone.jsの本家のページ。リファレンスサイトとしてすごく良い。)
- https://github.com/documentcloud/backbone(backbone.jsのソースは1500行弱と比較的少なめで読みやすかった)

個人的には、「日本語で8回分学ぶ → サンプル実装してみる → 詳しくはbackbone.jsのリファレンスを引く → 更に詳しく学ぶ為にソースコードを除く」の流れでの学習が良かったです。



最後に

一日で学んだ内容のため内容が薄いかもしれませんが、Backbone.jsの内容・よい点・気になる点をまとめることが出来てよかったです。
(間違いがありましたら、ご指摘頂けると幸いです)
先述にも書きましたが、Backbone.jsは、MVCモデルでの設計が出来る仕組みを提供してくれている一方、その上で奇麗な設計が出来るかは利用者次第。
よい設計を少しずつ学びたいと思います。

Backbone.jsのグッドプラクティスを身につけたら、ブログに書きたいと思います。
最後までご覧頂きましてありがとうございました。





こんな記事もいかがですか?

RSS画像

もしご興味をお持ち頂けましたら、ぜひRSSへの登録をお願い致します。