视图的部分渲染
为了最小化 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>
注意 class model-<%= 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 不够熟悉,无法提供准确的代码片段。