这个问题的要点是,如果我使用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);
}
如果我单击其中一个PostListViews,那么路由器将PostListView加载到页面上,如果我单击后退按钮,它会将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,但如果我单击列表中的一个成员然后单击后面,则不会在页面上加载帖子视图。我在检查控制台时注意到,在这种情况下(点击后退按钮后),el
未设置为#main
,这就是为什么它没有进入页面。在我不清理僵尸视图的代码中,如果我点击后退按钮,el
仍设置为#main
- 显然,好像我的代码要清理一下如果我点击后退按钮,僵尸视图会阻止#main被设置。所以,通过清理僵尸,我已经删除了el并且它没有再次设置,因为当我点击back
按钮时没有运行构造函数。
问题:有没有办法使用清理僵尸视图的路由器代码,但同时确保在我点击浏览器中的后退按钮时运行构造函数?如果即使我按下后退按钮也运行了视图的构造函数,那么我相信它会设置元素(就像在页面加载时那样)
答案 0 :(得分:1)
我无法从您的代码中看到您调用View.remove()但是假设它在this.currentView.close()中 - 删除函数(http://backbonejs.org/#View-remove)执行正确的清理但是也删除了#main元素从页面。因此,如果您有更多共享相同(现有)元素的视图,则无法调用remove。你必须通过调用undelegateEvents(用于DOM清理),stopListening(用于模型事件清理)来进行清理,如果没有立即渲染另一个视图,你也可以调用它。$ el.html(“”)来清空它。 / p>
我建议在覆盖路由器的执行功能(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中删除元素!),并在取消绑定所有Model事件的视图上调用stopListening()(我在这里指的是最新的Backbone 1.2.3)。
所以你通常在#2类型视图上使用remove()。如果你有#1类型视图,你也可以通过remove()来销毁它,但是你不能创建另一个实例,因为该元素不再存在(除非你通过某种外部机制创建新的实例,这可能会给代码带来麻烦。 )而不是删除你必须通过调用DOM事件的view.undelegateEvents()和view.stopListening()来杀死有界模型事件来进行清理。现在元素保留在DOM中,你可以创建新的视图实例,而前一个视图RIP没有鬼影。
BTW:你也可以有多个视图共享同一个元素 - 一个用于渲染,另一个用于处理(逻辑分组)事件 - 如果你不想只关闭其中一个,你必须使用非删除清理(或暂时切换一些行为。)关于你的情况 - 它是我刚才描述的组合:
// ..
// #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();
}
});