在Backbone.js中处理视图和模型对象的销毁

63

在不需要使用model/view实例时,最有效的处理方式是什么?

通常,我会将所有逻辑放在控制器/路由器中。它是决定应该创建哪些视图以及应向它们提供哪些模型的实例。通常,有几个处理程序函数,对应不同的用户操作或路由,当处理程序执行时,我会每次创建新的视图实例。当然,这应该会消除我之前存储在视图实例中的任何内容。然而,有些情况下,一些视图保留了DOM事件处理程序本身,它们没有得到正确解绑,导致这些实例仍然存在。我希望如果有一种适当的方式可以销毁视图实例,例如当它们的el(DOM表示)被分离或从DOM中移除时。


如果我想通过更改模型来使用相同的视图,那么只需要在将新模型分配给视图之前解除绑定模型,这样就足够了吗? - user995416
3个回答

77

你走在正确的道路上。你应该有一个控制视图生命周期的对象。我不喜欢将此放入我的视图中,而是喜欢为此创建一个单独的对象。

你需要做的是在必要时解绑事件。为了实现这一点,最好为所有视图创建一个“关闭”方法,并使用控制所有内容生命周期的对象始终调用该关闭方法。

例如:


  function AppController(){
    this.showView = function (view){
      if (this.currentView){
        this.currentView.close();
      }
      this.currentView = view;
      this.currentView.render();
      $("#someElement").html(this.currentView.el);
    }
  }

此时,您需要设置代码只有一个AppController实例,并且您将始终从路由器或任何需要在屏幕的#someElement部分中显示视图的其他代码调用appController.showView(...)

(我还有另一个非常简单的使用“AppView”的backbone应用程序示例(backbone视图运行应用程序的主要部分),请参见:http://jsfiddle.net/derickbailey/dHrXv/9/

默认情况下,视图上不存在close方法,因此您需要为每个视图自己创建一个。关闭方法应始终包含两个内容:this.unbind()this.remove()。此外,如果您将视图绑定到任何模型或集合事件,则应在关闭方法中取消绑定它们。

例如:


  MyView = Backbone.View.extend({
    initialize: function(){
      this.model.bind("change", this.modelChanged, this);
    },

    modelChanged: function(){
      // ... do stuff here
    },

    close: function(){
      this.remove();
      this.unbind();
      this.model.unbind("change", this.modelChanged);
    }
  });

使用this.remove()会正确地清理掉DOM中的所有事件,使用this.unbind()会清理掉视图本身可能引发的所有事件,使用this.model.unbind(...)会清理掉从模型绑定的事件。


我遇到了问题...http://stackoverflow.com/questions/19644259/how-to-correctly-remove-whatever-is-necessary-from-backbone-view 当我尝试在事件触发后创建此函数或调用this.remove等时,页面似乎无法渲染或者已经删除了要呈现的视图。 - Lion789
一定有 this.currentView = null; 这句话吧? - eugene

13

一种更简单的方法是在Backbone.View对象上添加自定义关闭方法

Backbone.View.prototype.close = function () {
  this.$el.empty();
  this.unbind();
};

使用上述代码,您可以执行以下操作。
var myView = new MyView();

myView.close();

易如反掌。


2
PS:我选择使用this.$el.empty();而不是this.$el.remove()因为 .remove() 会将父级 $el 从 DOM 中移除,而我需要该元素用于后续视图。如果您确实想要从 DOM 中删除视图定义的 el,则可以使用 .remove() 而不是 .empty()。 - Shaheen Ghiassy
1
太棒了!我自己也曾经苦恼于从DOM中删除$el并且因为无法建立后续视图而疯狂调试。我甚至不知道$el.empty()的存在,还创造了一些愚蠢的解决方法。 - Not a machine

6

我通常会清除视图,有时会重用模型。如果保留模型,则确保视图已被解除分配可能很麻烦。如果没有正确解绑,模型可能会保留对视图的引用。

从Backbone ~0.9.9开始,使用view.listenTo()而不是model.on()来绑定模型,可以通过控制反转(视图控制绑定而不是模型)更轻松地进行清理。如果使用view.listenTo()进行绑定,则调用view.stopListening()或view.remove()将删除所有绑定。类似于调用model.off(null, null, this)。

我喜欢通过扩展视图并添加一个close函数来清除视图中的子视图。子视图必须由父级属性引用,或者它们必须添加到名为childViews[]的父级数组中。

这是我使用的close函数..

// fired by the router, signals the destruct event within top view and 
// recursively collapses all the sub-views that are stored as properties
Backbone.View.prototype.close = function () {

    // calls views closing event handler first, if implemented (optional)
    if (this.closing) {
        this.closing();  // this for custom cleanup purposes
    }

    // first loop through childViews[] if defined, in collection views
    //  populate an array property i.e. this.childViews[] = new ControlViews()
    if (this.childViews) {
        _.each(this.childViews, function (child) {
            child.close();
        });
    }

    // close all child views that are referenced by property, in model views
    //  add a property for reference i.e. this.toolbar = new ToolbarView();
    for (var prop in this) {
        if (this[prop] instanceof Backbone.View) {
            this[prop].close();
        }
    }

    this.unbind();
    this.remove();

    // available in Backbone 0.9.9 + when using view.listenTo, 
    //  removes model and collection bindings
    // this.stopListening(); // its automatically called by remove()

    // remove any model bindings to this view 
    //  (pre Backbone 0.9.9 or if using model.on to bind events)
    // if (this.model) {
    //  this.model.off(null, null, this);
    // }

    // remove and collection bindings to this view 
    //  (pre Backbone 0.9.9 or if using collection.on to bind events)
    // if (this.collection) {
    //  this.collection.off(null, null, this);
    // }
}

然后按照以下方式声明一个视图..
views.TeamView = Backbone.View.extend({

    initialize: function () {
        // instantiate this array to ensure sub-view destruction on close()
        this.childViews = [];  

        this.listenTo(this.collection, "add", this.add);
        this.listenTo(this.collection, "reset", this.reset);

        // storing sub-view as a property will ensure destruction on close()
        this.editView = new views.EditView({ model: this.model.edits });
        $('#edit', this.el).html(this.editView.render().el);
    },

    add: function (member) {
        var memberView = new views.MemberView({ model: member });
        this.childViews.push(memberView);    // add child to array

        var item = memberView.render().el;
        this.$el.append(item);
    },

    reset: function () {
        // manually purge child views upon reset
        _.each(this.childViews, function (child) {
            child.close();
        });

        this.childViews = [];
    },

    // render is called externally and should handle case where collection
    // was already populated, as is the case if it is recycled
    render: function () {
        this.$el.empty();

        _.each(this.collection.models, function (member) {
            this.add(member);
        }, this);
        return this;
    }

    // fired by a prototype extension
    closing: function () {
        // handle other unbinding needs, here
    }
});

很好,这非常酷!我喜欢针对 instanceof Backbone.View 的属性检查,做得好。 - Kyle Patterson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接