SubView的なモノのより良い管理方法 (Backbone Advent Calendar 2012 24th day)

前段

海外のイケメンたちが書いた記事からくみ取ったパターンを、ひっじょーに薄めて紹介します。SubViewの中身までは及ばず、単純にMainViewが所有する要素の中で、SubViewをrenderするときの簡単な定義について。

MainViewの中にSubViewを設ける

MainView(ページ全体を司るView)の中に、SubView(部分的なView)を埋め込むときのパターンについて。より具体的には、初日に紹介した構成で言うところの、PageViewとPartialViewに該当しますが、ここでは単純にMain/Subとします。

原始的パターン

おおよそ原始的には、このようなパターンになります。

/**
 * @class MainView
 */
var MainView = Backbone.View.extend({
  render: function() {
    this.$el.html(this.template());
    this.subview = new SubView();
    this.$el.find('#subview-container').append(this.subview.render().$el);
    return this;
  }
});

MainViewをrenderして、SubViewが入るべき要素が生成されたときにSubViewを初期化して、所定の位置にぶっ込むというスタンス。まあまあ、素直なのですが、いくらか効率的でない点があります。

  • MainViewがリフレッシュされるたびにSubViewがinitializeされるオーバーヘッド
  • もしかしたら解放されないSubViewの中のイベントリスナ
  • renderメソッドの中が分厚くなって肥大化する恐れ

Badとは言いませんが、まあまあ微妙な感じなので、ちょっと洗練させてみましょう

現実的パターン

役割を少し分離させて、View本体の初期化が1度になるようにします。

/**
 * @class MainView
 */
var MainView = Backbone.View.extend({
  initialize: function() {
    this.subview = new SubView();
  },
  render: function () {
    this.$el.html(this.template());
    this.subview.setElement(this.$('#subview')).render();
    return this;
  }
});

よいかんじです。newする(initializeが発生する)のは1度で済むようになりました。ViewのsetElementメソッドのおかげで、これは当該Viewが管理する要素を再設定するメソッドです。このメソッドはそれなりに賢く、元の要素に対してはundelegateEventsをして、新しい要素には改めてdelegateEventsをします。

要素を付け替える度に、イベントリスナをリセットしてくれる感じです

抽象化

これを抽象化して細かいロジックを隠蔽すると、このようになります。

/**
 * @class MainView
 */
var MainView = Backbone.View.extend({
  initialize: function() {
    this.subview = new SubView();
  },
  render: function() {
    this.$el.html(this.template());
    this.assign(this.subview, '#subview')
    return this;
  },
  assign: function(view, selector) {
    view.setElement(this.$(selector)).render();
  }
});

こんな感じですね。assignメソッドによってSubViewの要素を再設定するあたりがまとめられています。次のようなパターンも考えられます。

/**
 * @class BaseView
 */
var BaseView = Backbone.View.extend({
  assignedViews: {},
  render: function() {
    var that = this;
    this.$el.html(this.presenter());
    Object.keys(this.assignedViews).forEach(function(selector) {
      that.assignedViews[selector].setElement(that.$el.find(selector));
    });
    return this;
  },
  assign: function(selector, view) {
    this.assignedViews[selector] = view;
  }
});

/**
 * @class MainView
 * @extends BaseView
 */
var MainView = BaseView.extend({
  initialize: function() {
    this.subview = new SubView();
    this.assign('#subview', this.subview);
  },
  presenter: function() {
    return this.template();
  }
});

処理を親クラスのメソッドに追い出していくと、個別に書く処理をさらに薄くできます。前回紹介したrenderpresenterの分離を併用しています。renderするときに、assignしておいたSubViewをまとめてsetElementしてrenderしています。

そんな感じ

えー、そんな感じで1日遅れの24日目のBackbone.js Advent Calendar 2012でございました。

MainViewからSubViewを操作するのに、イベントを介すのは面倒こともあるので、二度手間のように見えても、MainViewのプロパティとしてSubViewを提供し、その上でassign(setElement)したほうがよろしい感じです。参照距離は近いほうが良いですね。

内包要素でなく、まったく異なる要素(Action的なモノとか)を扱う場合はまた違うのですが、Partial的に扱うSubViewであれば、こんな感じをベースとして管理できます。

さて、25日目どうしよう...:;(∩´﹏`∩);: