在 Backbone 中删除和取消绑定子视图
Remove and unbind subviews in Backbone
我正在使用 Backbone 1.1.2,我发现我的应用程序的一些奇怪行为可能是由于 zombieviews。我读了 "Run! Zombies!" article from Derick Bailey 但后来发现这是为 Backbone 的旧版本编写的(如果我是正确的,则为 0.9)。
然后我发现对于较新的 Backbone 版本,对视图执行 .remove() 就足以正确地杀死它们(因为与 ListenTo 绑定的事件将通过调用 StopListening 自动删除).
在我的应用程序中,我有一个全局视图,它在某些时候创建了两个子视图。单击重置按钮(在全局视图中)时,应重新呈现这些视图(但可能首先 removed/unbound 以防止僵尸视图)。
所以我所做的是将子视图附加到全局视图可访问的列表中。在初始化函数中:
this._views = []; // empty list
在渲染子视图时,我将它们添加到列表中
v = new app.gameView();
this._views.push(v);
就在重新渲染子视图之前,我调用了一个循环遍历子视图列表的函数 cleanUp,并为每个子视图执行 .remove() 后跟 .unbind():
_.each(this._views, function(view){
this.remove();
this.unbind();
});
this._views = []; // empty the list for next use
我的问题是双重的:
- 调用 .remove 和 .unbind 是否足以防止僵尸视图?
- 将子视图添加到全局视图中的列表是否是执行此操作的正确方法?
如有任何想法,我们将不胜感激!
根据我的经验,只需调用 remove()
和 unbind()/off()
就足以防止 "zombies" 徘徊。我唯一要补充的是,如果父视图(包含 this._views
内的子视图的视图)被应用程序的另一部分引用,那么您有确保通过简单地将这些变量分配给 null 来删除这些引用。
在父级内部有一个 this._views
数组来保存它的子视图是完全没问题的。但是,随着您的应用程序的增长,您可能想要创建某种子视图管理器和一个核心视图,所有其他视图继承自。
这是我过去所做的;希望对您有所帮助:
CoreView:
// Probably all views should inherit from CoreView.
define([
'jquery',
'backbone',
'subviews'
], function($, Backbone, Subviews) {
var CoreView = Backbone.View.extend({
$main: $('#main'),
// Create an empty `subviews` property on all views.
constructor: function() {
this.subviews = new Subviews(this);
// Since we're overriding `constructor` here,
// we need to delegate to Backbone
Backbone.View.prototype.constructor.apply(this, arguments);
},
// Views should be attached to the DOM only with the `attach` method to have the right events thrown.
// Attach the view's element only if it's not already in the DOM.
attach: function() {
if (!this.isAttached()) {
this.$main.append(this.el);
this.trigger('dom:attach');
}
return this;
},
isAttached: function() {
return $.contains(document.body, this.el);
},
// Remove each view's subviews and clean up various properties before
// calling Backbone's remove() method.
remove: function() {
if (this.subviews.size()) {
this.subviews.removeAll();
}
// Remove the DOM element (jQuery makes sure to clean up DOM element's data)
Backbone.View.prototype.remove.apply(this, arguments);
// Fire a helpful `dom:detach` event when the view is removed from the DOM.
this.trigger('dom:detach');
this.off();
return this;
}
});
return CoreView;
});
子视图管理器(未完成):
// Simple Subview Manager
define([
'jquery',
'backbone'
], function($, Backbone) {
function Subviews(view) {
this.self = view; // this view
this._entries = {}; // its subviews
}
Subviews.prototype = {
constructor: Subviews,
add: function(name, viewInstance) { ... },
has: function(name) { return !!this._entries[name]; },
get: function(name) { return this._entries[name] && this._entries[name]; },
// By default the subview manager tries to replace an element with
// a `data-subview` attribute with the actual subview element.
attach: function(name) {
// In the parent view's template you would have: `<div data-subview="child1"></div>`
var $subViewOutput = this.self.$('[data-subview="'+name+'"]');
if (this._entries[name] && $subViewOutput.length) {
$subViewOutput.replaceWith(this._entries[name].render().el);
}
},
// When removing a subview we also have to remove it from
// this view's `subviews` property.
remove: function(name) {
if (this._entries && this._entries[name]) {
this._entries[name].remove();
// Cleanup
this._entries[name] = null;
this._entries = _.omit(this._entries, name);
}
},
removeAll: function() {
if (this.size()) {
_.each(this._entries, function(view) {
view.remove(); // it will call remove() in CoreView first
});
}
this._entries = {};
this.self = null;
},
size: function() {
return _.size(this._entries);
}
};
return Subviews;
});
普通视图:
define([
'jquery',
'backbone',
'templates',
'views/coreView',
'views/childView'
],
function($, Backbone, templates, CoreView, ChildView) {
var Widget = CoreView.extend({
tagName: 'section',
id: 'widget123',
template: templates.widget123,
initialize: function() {
this.subviews.add('child1', new ChildView());
this.on('dom:attach', function() {
// When the parent is inserted into the DOM also insert its child1
this.subviews.attach('child1');
});
},
render: function() {
this.$el.html(this.template());
return this;
}
});
var instance = new Widget();
instance.render().attach(); // attach() comes from CoreView
});
我正在使用 Backbone 1.1.2,我发现我的应用程序的一些奇怪行为可能是由于 zombieviews。我读了 "Run! Zombies!" article from Derick Bailey 但后来发现这是为 Backbone 的旧版本编写的(如果我是正确的,则为 0.9)。
然后我发现对于较新的 Backbone 版本,对视图执行 .remove() 就足以正确地杀死它们(因为与 ListenTo 绑定的事件将通过调用 StopListening 自动删除).
在我的应用程序中,我有一个全局视图,它在某些时候创建了两个子视图。单击重置按钮(在全局视图中)时,应重新呈现这些视图(但可能首先 removed/unbound 以防止僵尸视图)。
所以我所做的是将子视图附加到全局视图可访问的列表中。在初始化函数中:
this._views = []; // empty list
在渲染子视图时,我将它们添加到列表中
v = new app.gameView();
this._views.push(v);
就在重新渲染子视图之前,我调用了一个循环遍历子视图列表的函数 cleanUp,并为每个子视图执行 .remove() 后跟 .unbind():
_.each(this._views, function(view){
this.remove();
this.unbind();
});
this._views = []; // empty the list for next use
我的问题是双重的:
- 调用 .remove 和 .unbind 是否足以防止僵尸视图?
- 将子视图添加到全局视图中的列表是否是执行此操作的正确方法?
如有任何想法,我们将不胜感激!
根据我的经验,只需调用 remove()
和 unbind()/off()
就足以防止 "zombies" 徘徊。我唯一要补充的是,如果父视图(包含 this._views
内的子视图的视图)被应用程序的另一部分引用,那么您有确保通过简单地将这些变量分配给 null 来删除这些引用。
在父级内部有一个 this._views
数组来保存它的子视图是完全没问题的。但是,随着您的应用程序的增长,您可能想要创建某种子视图管理器和一个核心视图,所有其他视图继承自。
这是我过去所做的;希望对您有所帮助:
CoreView:
// Probably all views should inherit from CoreView.
define([
'jquery',
'backbone',
'subviews'
], function($, Backbone, Subviews) {
var CoreView = Backbone.View.extend({
$main: $('#main'),
// Create an empty `subviews` property on all views.
constructor: function() {
this.subviews = new Subviews(this);
// Since we're overriding `constructor` here,
// we need to delegate to Backbone
Backbone.View.prototype.constructor.apply(this, arguments);
},
// Views should be attached to the DOM only with the `attach` method to have the right events thrown.
// Attach the view's element only if it's not already in the DOM.
attach: function() {
if (!this.isAttached()) {
this.$main.append(this.el);
this.trigger('dom:attach');
}
return this;
},
isAttached: function() {
return $.contains(document.body, this.el);
},
// Remove each view's subviews and clean up various properties before
// calling Backbone's remove() method.
remove: function() {
if (this.subviews.size()) {
this.subviews.removeAll();
}
// Remove the DOM element (jQuery makes sure to clean up DOM element's data)
Backbone.View.prototype.remove.apply(this, arguments);
// Fire a helpful `dom:detach` event when the view is removed from the DOM.
this.trigger('dom:detach');
this.off();
return this;
}
});
return CoreView;
});
子视图管理器(未完成):
// Simple Subview Manager
define([
'jquery',
'backbone'
], function($, Backbone) {
function Subviews(view) {
this.self = view; // this view
this._entries = {}; // its subviews
}
Subviews.prototype = {
constructor: Subviews,
add: function(name, viewInstance) { ... },
has: function(name) { return !!this._entries[name]; },
get: function(name) { return this._entries[name] && this._entries[name]; },
// By default the subview manager tries to replace an element with
// a `data-subview` attribute with the actual subview element.
attach: function(name) {
// In the parent view's template you would have: `<div data-subview="child1"></div>`
var $subViewOutput = this.self.$('[data-subview="'+name+'"]');
if (this._entries[name] && $subViewOutput.length) {
$subViewOutput.replaceWith(this._entries[name].render().el);
}
},
// When removing a subview we also have to remove it from
// this view's `subviews` property.
remove: function(name) {
if (this._entries && this._entries[name]) {
this._entries[name].remove();
// Cleanup
this._entries[name] = null;
this._entries = _.omit(this._entries, name);
}
},
removeAll: function() {
if (this.size()) {
_.each(this._entries, function(view) {
view.remove(); // it will call remove() in CoreView first
});
}
this._entries = {};
this.self = null;
},
size: function() {
return _.size(this._entries);
}
};
return Subviews;
});
普通视图:
define([
'jquery',
'backbone',
'templates',
'views/coreView',
'views/childView'
],
function($, Backbone, templates, CoreView, ChildView) {
var Widget = CoreView.extend({
tagName: 'section',
id: 'widget123',
template: templates.widget123,
initialize: function() {
this.subviews.add('child1', new ChildView());
this.on('dom:attach', function() {
// When the parent is inserted into the DOM also insert its child1
this.subviews.attach('child1');
});
},
render: function() {
this.$el.html(this.template());
return this;
}
});
var instance = new Widget();
instance.render().attach(); // attach() comes from CoreView
});