web-dev-qa-db-fra.com

Suppression des objets de vue et de modèle dans Backbone.js

Quelle est la manière la plus efficace de supprimer les instances de modèle/vue lorsqu'elles ne sont pas nécessaires?

Habituellement, je mets toute la logique dans le contrôleur/routeur. C'est lui qui décide, quelles vues doivent être créées et quels modèles doivent leur être fournis. Habituellement, il existe quelques fonctions de gestionnaire, correspondant à différentes actions ou itinéraires utilisateur, où je crée de nouvelles instances de vue à chaque fois qu'un gestionnaire est exécuté. Bien sûr, cela devrait éliminer tout ce que j'ai précédemment stocké dans l'instance de vue. Cependant, dans certaines situations, certaines vues conservent les gestionnaires d'événements DOM eux-mêmes et ne sont pas correctement dissociées, ce qui entraîne la conservation de ces instances. Je souhaite s'il y avait un moyen approprié de détruire les instances de vue, lorsque par exemple leur el (représentation DOM) se détache ou est jeté hors du DOM

64
user802232

vous êtes sur la bonne voie. vous devriez avoir un objet qui contrôle le cycle de vie de vos vues. je n'aime pas mettre cela à mon avis. j'aime créer un objet séparé pour cela.

la chose que vous devez faire est de dissocier les événements lorsque cela est nécessaire. pour ce faire, c'est une bonne idée de créer une méthode "close" sur toutes vos vues et d'utiliser l'objet qui contrôle le cycle de vie de tout pour toujours appeler la méthode close.

par exemple:


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

à ce stade, vous devez configurer votre code pour n'avoir qu'une seule instance de AppController, et vous appelez toujours appController.showView(...) depuis votre routeur ou tout autre code qui doit afficher une vue dans le #someElement de votre écran.

(J'ai un autre exemple d'une application de base très simple qui utilise un "AppView" (une vue de base qui exécute la partie principale de l'application), ici: http://jsfiddle.net/derickbailey/dHrXv/9 / )

la méthode close n'existe pas sur les vues par défaut, vous devez donc en créer une vous-même pour chacune de vos vues. Il y a deux choses qui devraient toujours être dans la méthode close: this.unbind() et this.remove(). en plus de cela, si vous liez votre vue à un événement de modèle ou de collection, vous devez les dissocier dans la méthode close.

par exemple:


  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);
    }
  });

cela nettoiera correctement tous les événements du DOM (via this.remove()), tous les événements que la vue elle-même peut déclencher (via this.unbind()) et l'événement lié à la vue à partir du modèle (via this.model.unbind(...)).

77
Derick Bailey

Une manière plus simple consiste à ajouter une méthode de fermeture personnalisée sur l'objet Backbone.View

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

En utilisant le code ci-dessus, vous pouvez effectuer les opérations suivantes

var myView = new MyView();

myView.close();

peasy facile.

13
Shaheen Ghiassy

Je nuque toujours les vues et réutilise parfois les modèles. S'assurer que les vues sont désallouées peut être pénible si vous conservez les modèles. Les modèles peuvent conserver une référence à la vue s'ils ne sont pas correctement liés.

Depuis Backbone ~ 0.9.9, la liaison de modèles avec view.listenTo () plutôt que model.on () permet un nettoyage plus facile grâce à l'inversion du contrôle (affiche les liaisons de contrôle par opposition aux modèles). Si view.listenTo () est utilisé pour se lier, alors un appel à view.stopListening () ou view.remove () supprimera toutes les liaisons. Similaire à l'appel de model.off (null, null, this).

J'aime nettoyer les vues en étendant la vue avec une fonction close qui appelle les sous-vues semi-automatiquement. Les sous-vues doivent être référencées par les propriétés du parent ou elles doivent être ajoutées à un tableau dans le parent appelé childViews [].

Voici la fonction close que j'utilise ..

// 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);
    // }
}

Ensuite, une vue est déclarée comme suit.

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
    }
});
6
djabraham