如果使用浏览器后退按钮,如何运行视图的构造函数



这个问题的要点是,如果我使用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();
  }
});

最新更新