Javascript Backbone.js:重新填充或重新创建视图?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7567404/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Backbone.js : repopulate or recreate the view?
提问by solendil
In my web application, I have a user list in a table on the left, and a user detail pane on the right. When the admin clicks a user in the table, its details should be displayed on the right.
在我的 Web 应用程序中,我的左侧表格中有一个用户列表,右侧有一个用户详细信息窗格。当管理员单击表中的用户时,其详细信息应显示在右侧。
I have a UserListView and UserRowView on the left, and a UserDetailView on the right. Things kind of work, but I have a weird behavior. If I click some users on the left, then click delete on one of them, I get successive javascript confirm boxes for all users that have been displayed.
我在左边有一个 UserListView 和 UserRowView,在右边有一个 UserDetailView。事情有点工作,但我有一个奇怪的行为。如果我单击左侧的一些用户,然后单击其中一个用户的删除,我会得到所有已显示用户的连续 javascript 确认框。
It looks like event bindings of all previously displayed views have not been removed, which seems to be normal. I should not do a new UserDetailView every time on UserRowView? Should I maintain a view and change its reference model? Should I keep track of the current view and remove it before creating a new one? I'm kind of lost and any idea will be welcome. Thank you !
看起来之前显示的所有视图的事件绑定都没有被移除,这似乎是正常的。我不应该每次都在 UserRowView 上创建一个新的 UserDetailView?我应该维护视图并更改其参考模型吗?我应该跟踪当前视图并在创建新视图之前将其删除吗?我有点迷茫,任何想法都会受到欢迎。谢谢 !
Here is the code of the left view (row display, click event, right view creation)
下面是左视图的代码(行显示、点击事件、右视图创建)
window.UserRowView = Backbone.View.extend({
tagName : "tr",
events : {
"click" : "click",
},
render : function() {
$(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
return this;
},
click : function() {
var view = new UserDetailView({model:this.model})
view.render()
}
})
And the code for right view (delete button)
以及右视图的代码(删除按钮)
window.UserDetailView = Backbone.View.extend({
el : $("#bbBoxUserDetail"),
events : {
"click .delete" : "deleteUser"
},
initialize : function() {
this.model.bind('destroy', function(){this.el.hide()}, this);
},
render : function() {
this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
this.el.show();
},
deleteUser : function() {
if (confirm("Really delete user " + this.model.get("login") + "?"))
this.model.destroy();
return false;
}
})
采纳答案by Derick Bailey
I blogged about this recently, and showed several things that I do in my apps to handle these scenarios:
我最近写了一篇关于这个的博客,并展示了我在我的应用程序中为处理这些场景所做的几件事:
回答by Johnny Oshika
I always destroy and create views because as my single page app gets bigger and bigger, keeping unused live views in memory just so that I can re-use them would become difficult to maintain.
我总是销毁和创建视图,因为随着我的单页应用程序变得越来越大,将未使用的实时视图保留在内存中以便我可以重用它们将变得难以维护。
Here's a simplified version of a technique that I use to clean-up my Views to avoid memory leaks.
这是我用来清理视图以避免内存泄漏的技术的简化版本。
I first create a BaseView that all of my views inherit from. The basic idea is that my View will keep a reference to all of the events to which it's subscribed to, so that when it's time to dispose the View, all of those bindings will automatically be unbound. Here's an example implementation of my BaseView:
我首先创建一个 BaseView,我的所有视图都继承自它。基本思想是我的 View 将保留对其订阅的所有事件的引用,以便在处理 View 时,所有这些绑定将自动解除绑定。这是我的 BaseView 的示例实现:
var BaseView = function (options) {
this.bindings = [];
Backbone.View.apply(this, [options]);
};
_.extend(BaseView.prototype, Backbone.View.prototype, {
bindTo: function (model, ev, callback) {
model.bind(ev, callback, this);
this.bindings.push({ model: model, ev: ev, callback: callback });
},
unbindFromAll: function () {
_.each(this.bindings, function (binding) {
binding.model.unbind(binding.ev, binding.callback);
});
this.bindings = [];
},
dispose: function () {
this.unbindFromAll(); // Will unbind all events this view has bound to
this.unbind(); // This will unbind all listeners to events from
// this view. This is probably not necessary
// because this view will be garbage collected.
this.remove(); // Uses the default Backbone.View.remove() method which
// removes this.el from the DOM and removes DOM events.
}
});
BaseView.extend = Backbone.View.extend;
Whenever a View needs to bind to an event on a model or collection, I would use the bindTo method. For example:
每当视图需要绑定到模型或集合上的事件时,我都会使用 bindTo 方法。例如:
var SampleView = BaseView.extend({
initialize: function(){
this.bindTo(this.model, 'change', this.render);
this.bindTo(this.collection, 'reset', this.doSomething);
}
});
Whenever I remove a view, I just call the dispose method which will clean everything up automatically:
每当我删除一个视图时,我只调用 dispose 方法,它会自动清理所有内容:
var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();
I shared this technique with the folks who are writing the "Backbone.js on Rails" ebook and I believe this is the technique that they've adopted for the book.
我与正在编写“Backbone.js on Rails”电子书的人分享了这项技术,我相信这是他们在本书中采用的技术。
Update: 2014-03-24
更新:2014-03-24
As of Backone 0.9.9, listenTo and stopListening were added to Events using the same bindTo and unbindFromAll techniques shown above. Also, View.remove calls stopListening automatically, so binding and unbinding is as easy as this now:
从 Backone 0.9.9 开始,使用上面显示的相同 bindTo 和 unbindFromAll 技术将 listenTo 和 stopListening 添加到事件中。此外,View.remove 会自动调用 stopListening,因此绑定和解除绑定现在就像这样简单:
var SampleView = BaseView.extend({
initialize: function(){
this.listenTo(this.model, 'change', this.render);
}
});
var sampleView = new SampleView({model: some_model});
sampleView.remove();
回答by Brian Genisio
This is a common condition. If you create a new view every time, all old views will still be bound to all of the events. One thing you can do is create a function on your view called detatch
:
这是一种常见的情况。如果每次都创建一个新视图,所有旧视图仍将绑定到所有事件。您可以做的一件事是在您的视图上创建一个名为 的函数detatch
:
detatch: function() {
$(this.el).unbind();
this.model.unbind();
Then, before you create the new view, make sure to call detatch
on the old view.
然后,在创建新视图之前,请确保调用detatch
旧视图。
Of course, as you mentioned, you can always create one "detail" view and never change it. You can bind to the "change" event on the model (from the view) to re-render yourself. Add this to your initializer:
当然,正如您所提到的,您始终可以创建一个“详细信息”视图并且永远不要更改它。您可以绑定到模型上的“更改”事件(从视图中)以重新渲染自己。将此添加到您的初始化程序:
this.model.bind('change', this.render)
Doing that will cause the details pane to re-render EVERY time a change is made to the model. You can get finer granularity by watching for a single property: "change:propName".
这样做将导致详细信息窗格在每次对模型进行更改时重新渲染。您可以通过观察单个属性来获得更精细的粒度:“change:propName”。
Of course, doing this requires a common model that the item View has reference to as well as the higher level list view and the details view.
当然,这样做需要item View 引用的通用模型以及更高级别的列表视图和详细信息视图。
Hope this helps!
希望这可以帮助!
回答by Ashan
To fix events binding multiple times,
要多次修复事件绑定,
$("#my_app_container").unbind()
//Instantiate your views here
Using the above line before instantiating the new Views from route, solved the issue I had with zombie views.
在从路由实例化新视图之前使用上面的行,解决了我在僵尸视图中遇到的问题。
回答by thomasdao
I think most people start with Backbone will create the view as in your code:
我认为大多数人从 Backbone 开始会像你的代码一样创建视图:
var view = new UserDetailView({model:this.model});
This code creates zombie view, because we might constantly create new view without cleanup existing view. However it's not convenient to call view.dispose() for all Backbone Views in your app (especially if we create views in for loop)
这段代码创建了僵尸视图,因为我们可能会在不清理现有视图的情况下不断创建新视图。但是,为应用程序中的所有主干视图调用 view.dispose() 并不方便(特别是如果我们在 for 循环中创建视图)
I think the best timing to put cleanup code is before creating new view. My solution is to create a helper to do this cleanup:
我认为放置清理代码的最佳时机是在创建新视图之前。我的解决方案是创建一个助手来执行此清理工作:
window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
if (typeof VM.views[name] !== 'undefined') {
// Cleanup view
// Remove all of the view's delegated events
VM.views[name].undelegateEvents();
// Remove view from the DOM
VM.views[name].remove();
// Removes all callbacks on view
VM.views[name].off();
if (typeof VM.views[name].close === 'function') {
VM.views[name].close();
}
}
VM.views[name] = callback();
return VM.views[name];
}
VM.reuseView = function(name, callback) {
if (typeof VM.views[name] !== 'undefined') {
return VM.views[name];
}
VM.views[name] = callback();
return VM.views[name];
}
Using VM to create your view will help cleanup any existing view without having to call view.dispose(). You can do a small modification to your code from
使用 VM 创建视图将有助于清理任何现有视图,而无需调用 view.dispose()。您可以从
var view = new UserDetailView({model:this.model});
to
到
var view = VM.createView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
So it is up to you if you want to reuse view instead of constantly creating it, as long as the view is clean, you don't need to worry. Just change createView to reuseView:
所以如果你想重用视图而不是不断地创建它取决于你,只要视图是干净的,你就不用担心。只需将 createView 更改为重用视图:
var view = VM.reuseView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
Detailed code and attribution is posted at https://github.com/thomasdao/Backbone-View-Manager
详细代码和归属发布在https://github.com/thomasdao/Backbone-View-Manager
回答by bento
One alternative is to bind, as opposed to creating a series of new views and then unbinding those views. You'd accomplish this doing something like:
一种替代方法是绑定,而不是创建一系列新视图然后取消绑定这些视图。您可以通过以下方式完成此操作:
window.User = Backbone.Model.extend({
});
window.MyViewModel = Backbone.Model.extend({
});
window.myView = Backbone.View.extend({
initialize: function(){
this.model.on('change', this.alert, this);
},
alert: function(){
alert("changed");
}
});
You'd set the model of myView to myViewModel, which would be set to a User model. This way, if you set myViewModel to another user (i.e., changing its attributes) then it could trigger a render function in the view with the new attributes.
您将 myView 的模型设置为 myViewModel,该模型将设置为用户模型。这样,如果您将 myViewModel 设置为另一个用户(即,更改其属性),那么它可能会在具有新属性的视图中触发渲染函数。
One problem is that this breaks the link to the original model. You could get around this by either using a collection object, or by setting the user model as an attribute of the viewmodel. Then, this would be accessible in the view as myview.model.get("model").
一个问题是这会破坏与原始模型的链接。您可以通过使用集合对象或将用户模型设置为视图模型的属性来解决此问题。然后,这将在视图中作为 myview.model.get("model") 访问。
回答by Robins Gupta
Use this method for clearing the child views and current views from memory.
使用此方法从内存中清除子视图和当前视图。
//FIRST EXTEND THE BACKBONE VIEW....
//Extending the backbone view...
Backbone.View.prototype.destroy_view = function()
{
//for doing something before closing.....
if (this.beforeClose) {
this.beforeClose();
}
//For destroying the related child views...
if (this.destroyChild)
{
this.destroyChild();
}
this.undelegateEvents();
$(this.el).removeData().unbind();
//Remove view from DOM
this.remove();
Backbone.View.prototype.remove.call(this);
}
//Function for destroying the child views...
Backbone.View.prototype.destroyChild = function(){
console.info("Closing the child views...");
//Remember to push the child views of a parent view using this.childViews
if(this.childViews){
var len = this.childViews.length;
for(var i=0; i<len; i++){
this.childViews[i].destroy_view();
}
}//End of if statement
} //End of destroyChild function
//Now extending the Router ..
var Test_Routers = Backbone.Router.extend({
//Always call this function before calling a route call function...
closePreviousViews: function() {
console.log("Closing the pervious in memory views...");
if (this.currentView)
this.currentView.destroy_view();
},
routes:{
"test" : "testRoute"
},
testRoute: function(){
//Always call this method before calling the route..
this.closePreviousViews();
.....
}
//Now calling the views...
$(document).ready(function(e) {
var Router = new Test_Routers();
Backbone.history.start({root: "/"});
});
//Now showing how to push child views in parent views and setting of current views...
var Test_View = Backbone.View.extend({
initialize:function(){
//Now setting the current view..
Router.currentView = this;
//If your views contains child views then first initialize...
this.childViews = [];
//Now push any child views you create in this parent view.
//It will automatically get deleted
//this.childViews.push(childView);
}
});