javascript - Eliminación de objetos de vista y modelo en Backbone.js
(3)
¿Cuál es la forma más eficiente de eliminar instancias de modelo / vista cuando no es necesario?
Por lo general, pongo toda la lógica en el controlador / enrutador. Es el que decide, qué vistas se deben crear y qué modelos se les deben proporcionar. Por lo general, hay algunas funciones de controlador, que corresponden a diferentes acciones o rutas de usuario, donde creo nuevas instancias de vista cada vez que se ejecuta un controlador. Por supuesto, eso debería eliminar todo lo que haya almacenado previamente en la instancia de vista. Sin embargo, hay algunas situaciones en las que algunas vistas mantienen controladores de eventos DOM por sí mismas, y no se desenlazan correctamente, lo que hace que esas instancias se mantengan activas. Desearía que existiera una forma adecuada de destruir las instancias de visualización, cuando, por ejemplo, su el (representación de DOM) se separe, o se descarte del DOM
Siempre destruyo las vistas y algunas veces vuelvo a usar los modelos. Asegurarse de que las vistas estén desasignadas puede ser complicado, si mantiene los modelos a su alrededor. Los modelos pueden mantener una referencia a la vista si no están desvinculados correctamente.
A partir de Backbone ~ 0.9.9, los modelos vinculantes con view.listenTo () en lugar de model.on () permiten una limpieza más fácil mediante la inversión del control (las vistas controlan los enlaces a diferencia de los modelos). Si view.listenTo () se utiliza para enlazar, una llamada a view.stopListening () o view.remove () eliminará todos los enlaces. Similar a llamar a model.off (null, null, this).
Me gusta limpiar vistas ampliando la vista con una función cercana que llama subvistas semi-automáticamente. Las subvistas deben estar referenciadas por las propiedades del elemento primario o deben agregarse a una matriz dentro del elemento primario llamado childViews [].
Aquí está la función cercana que uso ...
// 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);
// }
}
Entonces una vista se declara de la siguiente manera ..
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
}
});
Una forma sencilla es agregar un método de cierre personalizado en el objeto Backbone.View
Backbone.View.prototype.close = function () {
this.$el.empty();
this.unbind();
};
Usando el código anterior, puede hacer lo siguiente
var myView = new MyView();
myView.close();
pan comido.
estás en el camino correcto. debería tener un objeto que controle el ciclo de vida de sus vistas. no me gusta poner esto en mi punto de vista. me gusta crear un objeto separado para esto.
lo que necesita hacer es desvincular los eventos cuando sea necesario. para hacer esto, es una buena idea crear un método "cerrado" en todas sus vistas, y usar el objeto que controla el ciclo de vida de todo para llamar siempre al método close.
por ejemplo:
function AppController(){
this.showView = function (view){
if (this.currentView){
this.currentView.close();
}
this.currentView = view;
this.currentView.render();
$("#someElement").html(this.currentView.el);
}
}
en este punto, configuraría su código para tener solo una instancia de AppController, y siempre llamaría a appController.showView(...)
desde su enrutador o cualquier otro código que necesite mostrar una vista en la porción #someElement
de tu pantalla.
(Tengo otro ejemplo de una aplicación básica muy simple que utiliza una "AppView" (una vista de red troncal que ejecuta la parte principal de la aplicación), aquí: http://jsfiddle.net/derickbailey/dHrXv/9/ )
el método de close
no existe en las vistas de forma predeterminada, por lo que debe crear uno usted mismo, para cada una de sus vistas. Hay dos cosas que siempre deberían estar en el método close: this.unbind()
y this.remove()
. Además de estos, si vincula su vista a cualquier evento de modelo o colección, debe desvincularlos en el método de cierre.
por ejemplo:
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);
}
});
esto limpiará adecuadamente todos los eventos del DOM (a través de this.remove()
), todos los eventos que la vista misma puede generar (a través de this.unbind()
) y el evento que la vista se vincule desde el modelo ( a través de this.model.unbind(...)
).