ビューの部分レンダリング
DOM 階層の完全なレンダリングを最小限に抑えるために、特定のプロパティの更新を反映する特別なノードを DOM に設定できます。
名前のリストであるこの単純なアンダースコア テンプレートを使用してみましょう。
<ul>
<% _(children).each(function(model) { %>
<li>
<span class='model-<%= model.cid %>-name'><%= model.name %></span> :
<span class='model-<%= model.cid %>-name'><%= model.name %></span>
</li>
<% }); %>
</ul>
classmodel-<%= model.cid %>-name
に注意してください。これが注入ポイントになります。
次に、基本ビューを定義 (または Backbone.View を変更) して、更新時にこれらのノードに適切な値を入力します。
var V = Backbone.View.extend({
initialize: function () {
// bind all changes to the models in the collection
this.collection.on('change', this.autoupdate, this);
},
// grab the changes and fill any zone set to receive the values
autoupdate: function (model) {
var _this = this,
changes = model.changedAttributes(),
attrs = _.keys(changes);
_.each(attrs, function (attr) {
_this.$('.model-' + model.cid + '-' + attr).html(model.get(attr));
});
},
// render the complete template
// should only happen when there really is a dramatic change to the view
render: function () {
var data, html;
// build the data to render the template
// this.collection.toJSON() with the cid added, in fact
data = this.collection.map(function (model) {
return _.extend(model.toJSON(), {cid: model.cid});
});
html = template({children: data});
this.$el.html(html);
return this;
}
});
コレクションではなくモデルに対応するために、コードは少し異なります。http://jsfiddle.net/nikoshr/cfcDX/で遊ぶフィドル
DOM 操作の制限
サブビューへのレンダリングの委任はコストがかかる可能性があり、それらの HTML フラグメントを親の DOM に挿入する必要があります。さまざまなレンダリング方法を比較するこの jsperf テストを見てください。
その要点は、完全な HTML 構造を生成してからビューを適用する方が、ビューとサブビューを構築してからレンダリングをカスケードするよりもはるかに高速であるということです。例えば、
<script id="tpl-table" type="text/template">
<table>
<thead>
<tr>
<th>Row</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<% _(children).each(function(model) { %>
<tr id='<%= model.cid %>'>
<td><%= model.row %></td>
<td><%= model.name %></td>
</tr>
<% }); %>
</tbody>
</table>
</script>
var ItemView = Backbone.View.extend({
});
var ListView = Backbone.View.extend({
render: function () {
var data, html, $table, template = this.options.template;
data = this.collection.map(function (model) {
return _.extend(model.toJSON(), {
cid: model.cid
});
});
html = this.options.template({
children: data
});
$table = $(html);
this.collection.each(function (model) {
var subview = new ItemView({
el: $table.find("#" + model.cid),
model: model
});
});
this.$el.empty();
this.$el.append($table);
return this;
}
});
var view = new ListView({
template: _.template($('#tpl-table').html()),
collection: new Backbone.Collection(data)
});
http://jsfiddle.net/nikoshr/UeefE/
jsperf は、テンプレートをあまりペナルティなしでサブテンプレートに分割できることを示していることに注意してください。これにより、行の部分的なレンダリングを提供できます。
関連する注意として、DOM にアタッチされたノードでは動作しないでください。これにより、不要なリフローが発生します。ノードを操作する前に、新しい DOM を作成するか、ノードを切り離してください。
ゾンビをつぶす
Derick Bailey は、ゾンビ ビューの根絶に関する優れた記事を書きました。
基本的に、ビューを破棄するときは、すべてのリスナーのバインドを解除し、jQuery プラグイン インスタンスの破棄などの追加のクリーンアップを実行する必要があることを覚えておく必要があります。私が使用しているのは、Derick がBackbone.Marionetteで使用しているものと同様のメソッドの組み合わせです。
var BaseView = Backbone.View.extend({
initialize: function () {
// list of subviews
this.views = [];
},
// handle the subviews
// override to destroy jQuery plugin instances
unstage: function () {
if (!this.views) {
return;
}
var i, l = this.views.length;
for (i = 0; i < l; i = i + 1) {
this.views[i].destroy();
}
this.views = [];
},
// override to setup jQuery plugin instances
stage: function () {
},
// destroy the view
destroy: function () {
this.unstage();
this.remove();
this.off();
if (this.collection) {
this.collection.off(null, null, this);
}
if (this.model) {
this.model.off(null, null, this);
}
}
});
前の例を更新して、行にドラッグ可能な動作を与えると、次のようになります。
var ItemView = BaseView.extend({
stage: function () {
this.$el.draggable({
revert: "invalid",
helper: "clone"
});
},
unstage: function () {
this.$el.draggable('destroy');
BaseView.prototype.unstage.call(this);
}
});
var ListView = BaseView.extend({
render: function () {
//same as before
this.unstage();
this.collection.each(function (model) {
var subview = new ItemView({
el: $table.find("#" + model.cid),
model: model
});
subview.stage();
this.views.push(subview);
}, this);
this.stage();
this.$el.empty();
this.$el.append($table);
return this;
}
});
http://jsfiddle.net/nikoshr/yL7g6/
ルート ビューを破棄すると、ビューの階層が走査され、必要なクリーンアップが実行されます。
注意: JS コードについては申し訳ありません。正確なスニペットを提供できるほど Coffeescript に精通していません。