Backboneの小ネタなパターン3つ (Backbone Advent Calendar 2012 21th day)

小ネタなパターン

Backbone.js Advent Calendar 2012の21日目でございます。おもむろに小ネタなパターンを、3つほど簡単に紹介してみる次第。

renderとpresenterの分離パターン

これは Backbone.js Tips and Tricks を参考にしています。実践してみたところ、なにかと具合が良くてお気に入りのパターンです。

Backbone.View.extend({
  tmpl: 'path/to/tmpl',
  initialize: function() {
    this.model = new SomeModel();
    this.model.fetch({
      success: this.render
    });
  },
  presenter: function() {
    var data = this.model.toJSON();
    // data manipulation and formatting
    return data;
  },
  render: function() {
    var htmlString = doTemplating(this.tmpl, this.presenter());
    this.$el.html(htmlString);
    return this;
  }
});

こんな感じで、データの加工を行うpresenterと、実際にレンダリング処理を行うrenderを分割したパターンです。renderはBackboneの慣習的に役割を定められたメソッドですが、そこにpresenterを加えて役割を分担するだけというシンプルさです。

render
Viewの要素に実際のレンダリング(= HTML文字列の流し込み処理)を行う。仮にシングルページアプリケーションであれば、View切り替え時のエフェクトやチラつき防止の要素操作など、描画するその瞬間に関わる処理を記述する。
presenter
描画に必要なデータを取得し、レンダリング処理に適した状態にデータを加工したり追加したりする。例えば、APIから平たいArrayで返ってきている部分を、表現すべきデザインテンプレートに合わせて整形するような処理などを記述する。

上の例では、最低限の処理ですが、凝った処理が必要であれば基底クラスにrenderをもたせて共通化を進め、Viewごとに事情の違うデータ加工などの処理をpresenterで行うことで、良い感じに分担をすることができます。

RequireJSでSingletonっぽいパターン

2013/02/07追記: 見返したらこれ、RequireJS自身の機構と併せてFlyweightのように働いていると捉えたほうが自然かも。

BackboneアプリケーションでRequireJSを利用する場合、一般的には以下のようなパターンになるでしょう。

// models/location.js
define(['backbone'], function(Backbone) {
  return Backbone.Model.extend({
    initialize: function(_, args) {
      this.watchId = navigator.location.watchPosition(
        args.success,
        args.error,
        args.options
      );
    }
    // some properties and methods
  });
});

// views/search.js
define(['backbone', 'models/location'], function(Backbone, Location) {
  return Backbone.View.extend({
    initialize: function() {
      this.location = new Location();
    }
    // some properties and methods
  })
});

new Location()で、モデルのインスタンスを作成することになります。これでごくノーマルな感じなのですが、位置情報とか複数のインスタンスが存在する必要は無く、ひとつだけ生成されれば良かったりします。そこで..

// models/location.js
define(['backbone'], function(Backbone) {
  return new (Backbone.Model.extend({
    initialize: function(_, args) { /*...*/ }
    // some properties and methods
  }));
});

// views/search.js
define(['backbone', 'models/location'], function(Backbone, location) {
  return Backbone.View.extend({
    this.location = location
    // some properties and methods
  })
});

このように、Locationモデルをreturnする時点で、newしておけば、利用する側(ここではView)では常に同一のインスタンスのみ受け取ることができるようになります。

試しにググってみたら、こういうサンプルもありました。こちらはgetInstanceが、なんだか懐かしいような気持ちにさせてくれるパターンですね。そもそもRequireJSでSingletonって...という違和感は適当に乗り越えるなり、別のクールなパターンで解決していただければ。

Singleton パターン - Wikipediaについては百科事典をご覧くださいませ。

より良い継承とMixinのパターン

Developing Backbone.js Applications - Inheritance & Mixinsが非常に参考になります。

_人人人人人人人人人人_
> 参考になります! <
 ̄^Y^Y^Y^Y^Y^Y^Y^Y^ ̄

はい、なんか丸々引用するにも長い&アレなので、素直にリンク先をご覧くださいませ。基底クラスで共通化しておきたい初期化処理などを、うまく表現することができます。

JSとしてあまり書きたくないな、と思えるParentClass.prototype.initialize.call(this, arguments)とか、PHPer的にparent::__construct()みたいな趣あってなんだかノスタルジー。

おしまい

Viewのイベント管理とメモリリークなどのパターンもまとめかけましたが、最新版でそのあたりのメソッド提供の事情も変わってそうなので、次回にとっておく or 他の人にお任せすることにします。

おまけで、そのあたりのこと書いてる系URL。