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

时间:2015-10-04 01:41:15

标签: backbone.js

这个问题的要点是,如果我使用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按钮时没有运行构造函数。

问题:有没有办法使用清理僵尸视图的路由器代码,但同时确保在我点击浏览器中的后退按钮时运行构造函数?如果即使我按下后退按钮也运行了视图的构造函数,那么我相信它会设置元素(就像在页面加载时那样)

1 个答案:

答案 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();
  }

});