javascript - Wie man Unteransichten in Backbone.js rendert und anhängt
Ich habe eine verschachtelte View-Konfiguration, die in meiner Anwendung ziemlich tief werden kann. Es gibt viele Möglichkeiten, wie ich die Unteransichten initialisieren, rendern und anhängen könnte, aber ich frage mich, was die übliche Praxis ist.
Hier sind ein paar, an die ich gedacht habe:
initialize : function () {
this.subView1 = new Subview({options});
this.subView2 = new Subview({options});
},
render : function () {
this.$el.html(this.template());
this.subView1.setElement('.some-el').render();
this.subView2.setElement('.some-el').render();
}
Vorteile: Sie müssen sich beim Anhängen keine Sorgen machen, die richtige DOM-Reihenfolge einzuhalten. Die Ansichten werden frühzeitig initialisiert, sodass in der Render-Funktion nicht alles auf einmal erledigt werden muss.
Nachteile: Sie sind gezwungen, re-delegateEvents() auszuführen, was möglicherweise kostspielig ist? Die Render-Funktion der übergeordneten Ansicht ist mit allem Unteransichts-Rendering überladen, das stattfinden muss? Sie haben nicht die Möglichkeit, den tagName der Elemente festzulegen, sodass die Vorlage die richtigen tagNames beibehalten muss.
Eine andere Möglichkeit:
initialize : function () {
},
render : function () {
this.$el.empty();
this.subView1 = new Subview({options});
this.subView2 = new Subview({options});
this.$el.append(this.subView1.render().el, this.subView2.render().el);
}
Vorteile: Sie müssen Ereignisse nicht neu delegieren. Sie benötigen keine Vorlage, die nur leere Platzhalter enthält, und Ihre tagNames werden wieder von der Ansicht definiert.
Nachteile: Sie müssen nun sicherstellen, dass Sie Dinge in der richtigen Reihenfolge anhängen. Das Rendern der übergeordneten Ansicht ist immer noch durch das Rendern der Unteransicht überladen.
Mit einem onRender-Ereignis:
initialize : function () {
this.on('render', this.onRender);
this.subView1 = new Subview({options});
this.subView2 = new Subview({options});
},
render : function () {
this.$el.html(this.template);
//other stuff
return this.trigger('render');
},
onRender : function () {
this.subView1.setElement('.some-el').render();
this.subView2.setElement('.some-el').render();
}
Vorteile: Die Logik der Unteransicht ist jetzt vom render()-Methodenaufruf der Ansicht getrennt.
Mit einem onRender-Ereignis:
initialize : function () {
this.on('render', this.onRender);
},
render : function () {
this.$el.html(this.template);
//other stuff
return this.trigger('render');
},
onRender : function () {
this.subView1 = new Subview();
this.subView2 = new Subview();
this.subView1.setElement('.some-el').render();
this.subView2.setElement('.some-el').render();
}
Ich habe eine Art von verschiedenen Praktiken aus all diesen Beispielen gemischt und kombiniert (also tut mir dafür leid), aber welche würdest du beibehalten oder hinzufügen? Und was würdest du nicht tun?
Zusammenfassung der Praktiken:
- Subview-Instanzen in
initializeoder inrendererstellen? - Soll die gesamte Sub-View-Rendering-Logik in
renderoder inonRenderausgeführt werden? - Verwenden Sie
setElementoderappend/appendTo?
Antwort #1
Ich habe allgemein ein paar verschiedene Lösungen gesehen/verwendet:
Lösung 1
var OuterView = Backbone.View.extend({
initialize: function() {
this.inner = new InnerView();
},
render: function() {
this.$el.html(template); // or this.$el.empty() if you have no template
this.$el.append(this.inner.$el);
this.inner.render();
}
});
var InnerView = Backbone.View.extend({
render: function() {
this.$el.html(template);
this.delegateEvents();
}
});
Dies ähnelt Ihrem ersten Beispiel, mit einigen Änderungen:
- Die Reihenfolge, in der Sie die Unterelemente anhängen, ist wichtig
- Die äußere Ansicht enthält nicht die HTML-Elemente, die in der inneren Ansicht gesetzt werden sollen (das bedeutet, dass Sie immer noch tagName in der inneren Ansicht angeben können)
render()wird AUFGERUFEN, NACHDEM das Element der inneren Ansicht in das DOM eingefügt wurde, was hilfreich ist, wenn dierender()-Methode Ihrer inneren Ansicht sich auf der Seite basierend auf der Position/Größe anderer Elemente platziert/größt (was meiner Erfahrung nach ein häufiger Anwendungsfall ist)
Lösung 2
var OuterView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
this.$el.html(template); // or this.$el.empty() if you have no template
this.inner = new InnerView();
this.$el.append(this.inner.$el);
}
});
var InnerView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
this.$el.html(template);
}
});
Lösung 2 mag sauberer aussehen, aber in meiner Erfahrung hat sie einige seltsame Dinge verursacht und die Leistung negativ beeinflusst.
Ich verwende im Allgemeinen Lösung 1 aus ein paar Gründen:
- Viele meiner Ansichten hängen davon ab, bereits im DOM in ihrer
render()-Methode zu sein - Wenn die äußere Ansicht neu gerendert wird, müssen die Ansichten nicht neu initialisiert werden, was eine Neuinitialisierung Speicherlecks verursachen und auch seltsame Probleme mit bestehenden Bindungen verursachen kann
Beachte, dass wenn du jedes Mal, wenn render() aufgerufen wird, eine new View() initialisierst, diese Initialisierung sowieso delegateEvents() aufrufen wird. Das sollte also nicht unbedingt ein „Nachteil“ sein, wie du es ausgedrückt hast.
Antwort #2
Dies ist ein dauerhaftes Problem bei Backbone und meiner Erfahrung nach gibt es darauf keine wirklich befriedigende Antwort. Ich teile Ihre Frustration, besonders da es so wenig Anleitung gibt, obwohl dieser Anwendungsfall so häufig vorkommt. Das gesagt, gehe ich normalerweise mit etwas Ähnlichem wie Ihrem zweiten Beispiel vor.
Zunächst einmal würde ich alles, was erfordert, dass Sie Ereignisse neu zuordnen, sofort verwerfen. Das ereignisgesteuerte View-Modell von Backbone ist eine seiner wichtigsten Komponenten, und diese Funktionalität zu verlieren, nur weil Ihre Anwendung nicht trivial ist, würde jedem Programmierer einen schlechten Nachgeschmack hinterlassen. Also streichen Sie Punkt eins.
Bezüglich Ihres dritten Beispiels denke ich, dass es nur ein Umgehen der herkömmlichen Render-Praxis ist und nicht viel Bedeutung hinzufügt. Vielleicht wäre es, wenn Sie tatsächlich Ereignisse auslösen (d.h. nicht ein konstruiertes „onRender“-Ereignis), lohnenswert, diese Ereignisse direkt an render zu binden. Wenn Sie feststellen, dass render unüberschaubar und komplex wird, haben Sie zu wenige Unteransichten.
Zurück zu Ihrem zweiten Beispiel, das wahrscheinlich das kleinere von drei Übeln ist. Hier ist Beispielcode aus Recipes With Backbone, gefunden auf Seite 42 meiner PDF-Ausgabe:
...
render: function() {
$(this.el).html(this.template());
this.addAll();
return this;
},
addAll: function() {
this.collection.each(this.addOne);
},
addOne: function(model) {
view = new Views.Appointment({model: model});
view.render();
$(this.el).append(view.el);
model.bind('remove', view.remove);
}
Dies ist nur eine etwas raffiniertere Einrichtung als Ihr zweites Beispiel: Sie spezifizieren eine Menge von Funktionen, addAll und addOne, die die Schmutzarbeit erledigen. Ich denke, dieser Ansatz ist praktikabel (und ich benutze ihn sicherlich); aber er hinterlässt immer noch einen seltsamen Nachgeschmack. (Verzeihen Sie all diese Zungenmetaphern.)
Zu Ihrem Punkt zum Anhängen in der richtigen Reihenfolge: Wenn Sie strikt anhängen, ja, das ist eine Einschränkung. Aber achten Sie darauf, alle möglichen Templating-Schemata zu berücksichtigen. Vielleicht möchten Sie tatsächlich ein Platzhalter-Element (z. B. ein leeres div oder ul) haben, das Sie dann mit replaceWith durch ein neues (DOM-)Element ersetzen können, das die entsprechenden Unteransichten enthält. Anhängen ist nicht die einzige Lösung, und Sie können das Reihenfolgeproblem sicherlich umgehen, wenn es Ihnen wirklich wichtig ist, aber ich würde vermuten, dass Sie ein Designproblem haben, wenn es Sie so sehr verwirrt. Denken Sie daran, Unteransichten können eigene Unteransichten haben, und sie sollten dies tun, wenn es angemessen ist. Auf diese Weise erhalten Sie eine eher baumartige Struktur, was ziemlich angenehm ist: Jede Unteransicht fügt alle ihre Unteransichten der Reihe nach hinzu, bevor die übergeordnete Ansicht eine weitere hinzufügt, und so weiter.
Leider ist Lösung #2 wahrscheinlich das Beste, was man mit dem fertigen Backbone erwarten kann. Wenn Sie daran interessiert sind, Drittanbieter-Bibliotheken auszuprobieren, ist eine, die ich mir angesehen habe (aber noch keine Zeit hatte, wirklich auszuprobieren) Backbone.LayoutManager, der anscheinend eine bessere Methode zum Hinzufügen von Unteransichten bietet. Allerdings gab es auch bei ihnen kürzlich Diskussionen über ähnliche Probleme wie diese.
