这个问题的要点是,如果我使用Backbone.js代码来清理我的视图(即防止僵尸视图),那么如果我在浏览器中单击后退按钮,我就无法设置视图的元素,因此视图在我单击后退按钮的情况下无法正确渲染。
详细信息我有一个显示帖子列表的PostsView。在构造函数中,我调用setElement,然后使用多个PostListViews呈现列表的每个成员
export class PostsView extends Backbone.View{
constructor(options){
this.setElement($('#main'), true)
super()
this.render()
}
addEachPost(model){
new PostListView({model: model });
}
render(){
this.collection.each(this.addEachPost, this);
}
如果我点击其中一个PostListView,那么路由器会把那个PostListView加载到页面上,如果我点击后退按钮,它会把Posts视图重新加载到页面上,但前提是我没有清理Posts视图。例如,使用下面的代码(它不会清理视图),我可以点击后退按钮,然后点击另一个链接等等,它继续加载所有内容,但它最终会创建僵尸,因为我没有清理视图
路由器 export class MyRouter extends Backbone.Router{
constructor(options){
'': 'renderList',
'post/:id': 'showPost'
}
renderList(){
new PostsView({collection: this.collection)};
}
showPost(id){
//code ommitted
new PostView({model: modle})
}
}
因此,通过下面的代码,我引入了一些常用的代码(我在其他Backbone应用程序中见过)来防止僵尸视图,但是现在如果我这样做了,当我点击后退按钮时,PostsView不会被渲染到页面
路由器代码清理僵尸
changeView(view){
if (this.currentView){
this.currentView.close();
}
this.currentView = view;
}
在路由器中,然后通过调用
来加载视图 `this.changeView( new PostsView({collection: this.collection)});`
然而,如果我这样做,我可以首先加载PostsView,但是如果我单击列表中的一个成员,然后单击后面,则post视图不会加载到页面上。我从检查控制台注意到,在这种情况下(单击后退按钮后),el
没有设置为#main
,这就是为什么它没有进入页面。在我不清理僵尸视图的代码中,如果我点击后退按钮,el
仍然设置为#main
——所以显然,如果我点击后退按钮,似乎我的代码清理僵尸视图做了一些事情来防止#main被设置。所以,通过清理僵尸,我已经删除了el,它不会再次被设置,因为当我点击back
按钮时,构造函数没有运行。
问题:是否有一些方法可以使用Router代码来清理僵尸视图,但同时确保当我在浏览器中点击后退按钮时构造函数运行?如果视图的构造函数在我点击后退按钮时仍在运行,那么我相信它会设置元素(就像在页面加载时那样)
我不能从你的代码中看到,你调用View.remove(),但假设它在this.currentView.close() -删除函数(http://backbonejs.org/#View-remove)做适当的清理,但也从页面中删除#main元素。所以如果你有更多的视图共享相同的(现有的)元素,你不能调用remove。你必须通过调用undelegateEvents(用于DOM清理),stopplistening(用于模型事件清理)来做清理,如果另一个视图没有立即呈现,你也可以调用this.$el.html(")来清空它。
我建议在覆盖路由器的执行函数(http://backbonejs.org/#Router-execute)中做清理
编辑:当在主干中创建视图时,你有两种选择如何创建它的元素:
1)使用现有的DOM元素
var MyView = Backbone.View.extend({
// selector text..
$el: "#main",
render: function () {
// visible immediatelly
this.$el.html("<p>content</p>");
return this;
}
});
2)或者由视图创建新元素
var MyView = Backbone.View.Extend({
// div is default - you don't have to specify it...
tagName: 'div',
render: function () {
// not visible - this.$el is not live DOM element
this.$el.html("<p>content</p>");
return this;
}
});
// ..
// outer mechanism
var myView = new MyView();
$("body").append(myView.render().$el);
在第二种情况下,它不是活动的DOM元素,必须通过视图本身之外的某种机制附加到DOM中(因为视图应该只知道它的元素,而您需要使用上面的某些元素)
当销毁视图时,也有几种情况。如果你在视图上调用remove(),它会做所有需要的清理——它会调用jQuery的remove()来解除所有DOM事件的绑定(从DOM中删除元素!),还会在视图上调用stopplistening()来解除所有Model事件的绑定(我在这里引用最新的Backbone 1.2.3)。
所以你通常在#2类型视图上使用remove()。如果你有#1类型视图,你也可以通过remove()来销毁它,但你不能创建另一个实例,因为元素不再存在(除非你通过一些外部机制创建一个新的实例,这可能会很快给你的代码带来混乱…)而不是删除,你必须通过调用view.undelegateEvents()来清除DOM事件,调用view. stoppllistening()来清除有界模型事件。现在元素仍然在DOM中,你可以创建新的视图实例,而之前的视图RIP没有鬼。顺便说一下:你也可以有多个视图共享相同的元素-一个用于渲染,另一个用于处理(逻辑分组)事件-然后如果你不想关闭其中一个(或暂时切换某些行为),你必须使用非删除清理。
关于你的情况,这是我刚才描述的组合:
// ..
// #2 type view
var PostListItemView = Backbone.View.extend({
tagName: "li",
render: function () {
this.$el.html('<a href="#posts/' + this.model.getId() + '">' + this.model.getName() + '</a>');
return this;
}
});
// ..
// #1 type view
var PostDetailView = Backbone.View.extend({
$el: "#main",
render: function () {
this.$el.html(/* ... */);
return this;
},
close: function () {
this.undelegateEvents();
this.stopListening();
}
});
// ..
// #1 type view
var PostsListView = Backbone.View.extend({
$el: "#main",
render: function () {
var $list = $("<ul></ul>");
// you need to store reference to the item views
this.postListLtemViews = this.collection.reduce(function (list, item) {
var postListItemView = new PostListItemView({
model: item
});
// 'outer mechanism' to insert #2 type view to DOM
$list.append(postListItemView.render().$el);
list.push(postListItemView);
return list;
}, []);
this.$el.html($list);
return this;
},
close: function () {
_.each(this.postListLtemViews, function (postListItemView) {
postListItemView.remove();
});
this.undelegateEvents();
this.stopListening();
}
});
// ..
// Router
var App = Backbone.Router.extend({
routes: {
"posts": "postsList",
"posts/:id": "postDetail"
},
execute: function (callback, args, name) {
// Cleanup
if ( this.currentView ) {
this.currentView.close();
}
// Apply route function
if ( callback ) {
callback.apply(this, args);
}
},
postsList: function () {
this.currentView = new PostsListView({
collection: ...
});
this.currentView.render();
},
postDetail: function (id) {
this.currentView = new PostDetailView({
model: ...
});
this.currentView.render();
}
});