javascript - wp_enqueue_script - BackboneJS Rendering Probleme



wp_enqueue_script priority (2)

Teilweise Darstellung von Ansichten

Um das vollständige Rendering Ihrer DOM-Hierarchie zu minimieren, können Sie spezielle Knoten in Ihrem DOM einrichten, die Aktualisierungen für eine bestimmte Eigenschaft widerspiegeln.

Verwenden wir diese einfache Underscore-Vorlage, eine Liste von Namen:

<ul>
  <% _(children).each(function(model) { %>
    <li>
        <span class='model-<%= model.cid %>-name'><%= model.name %></span> :
        <span class='model-<%= model.cid %>-name'><%= model.name %></span>
    </li>
  <% }); %>
</ul>

Beachten Sie das Klassenmodell model-<%= model.cid %>-name , dies wird unser Injektionspunkt sein.

Wir können dann eine Basisansicht definieren (oder Backbone.View ändern), um diese Knoten mit den entsprechenden Werten zu füllen, wenn sie aktualisiert werden:

var V = Backbone.View.extend({
    initialize: function () {
        // bind all changes to the models in the collection
        this.collection.on('change', this.autoupdate, this);
    },

    // grab the changes and fill any zone set to receive the values
    autoupdate: function (model) {
        var _this = this,
            changes = model.changedAttributes(),
            attrs = _.keys(changes);

        _.each(attrs, function (attr) {
            _this.$('.model-' + model.cid + '-' + attr).html(model.get(attr));
        });
    },

    // render the complete template
    // should only happen when there really is a dramatic change to the view
    render: function () {
        var data, html;

        // build the data to render the template
        // this.collection.toJSON() with the cid added, in fact
        data = this.collection.map(function (model) {
            return _.extend(model.toJSON(), {cid: model.cid});
        });

        html = template({children: data});
        this.$el.html(html);

        return this;
    }
});

Der Code würde ein wenig variieren, um ein Modell anstelle einer Sammlung aufzunehmen. Eine Fiddle zum Spielen mit http://jsfiddle.net/nikoshr/cfcDX/

Begrenzung der DOM-Manipulationen

Das Delegieren des Renderings in die Untersichten kann kostspielig sein, da ihre HTML-Fragmente in das DOM des übergeordneten Elements eingefügt werden müssen. Sehen Sie sich diesen jsperf-Test an und vergleichen Sie verschiedene Rendermethoden

Der Kern davon ist, dass das Generieren der vollständigen HTML-Struktur und das anschließende Anwenden von Ansichten viel schneller ist als das Erstellen von Ansichten und Unteransichten und das anschließende Kaskadieren des Renderings. Beispielsweise,

<script id="tpl-table" type="text/template">
    <table>
        <thead>
            <tr>
                <th>Row</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
        <% _(children).each(function(model) { %>
            <tr id='<%= model.cid %>'>
                <td><%= model.row %></td>
                <td><%= model.name %></td>
            </tr>
        <% }); %>
        </tbody>
     </table>
</script>
var ItemView = Backbone.View.extend({
});

var ListView = Backbone.View.extend({
    render: function () {
        var data, html, $table, template = this.options.template;

        data = this.collection.map(function (model) {
            return _.extend(model.toJSON(), {
                cid: model.cid
            });
        });

        html = this.options.template({
            children: data
        });

        $table = $(html);

        this.collection.each(function (model) {
            var subview = new ItemView({
                el: $table.find("#" + model.cid),
                model: model
            });
        });

        this.$el.empty();
        this.$el.append($table);

        return this;
    }
});


var view = new ListView({
    template: _.template($('#tpl-table').html()),
    collection: new Backbone.Collection(data)
});

http://jsfiddle.net/nikoshr/UeefE/

Beachten Sie, dass jsperf zeigt, dass die Vorlage ohne zu viel Penalty in Subtemplates aufgeteilt werden kann, wodurch Sie ein partielles Rendering für die Zeilen bereitstellen können.

Zu diesem Zweck arbeiten Sie nicht mit Knoten, die an das DOM angeschlossen sind. Dies führt zu unnötigen Reflow-Vorgängen. Erstellen Sie entweder ein neues DOM oder trennen Sie den Knoten, bevor Sie ihn manipulieren.

Zombies quetschen

Derick Bailey schrieb einen ausgezeichneten Artikel über das Ausrotten von Zombie-Ansichten

Grundsätzlich müssen Sie sich daran erinnern, dass Sie alle Listener lösen müssen, wenn Sie eine Ansicht verwerfen, und zusätzliche Bereinigungen wie das Löschen der jQuery-Plugin-Instanzen durchführen. Was ich benutze, ist eine Kombination von Methoden ähnlich denen, die Derick in Backbone.Marionette :

var BaseView = Backbone.View.extend({

    initialize: function () {
        // list of subviews
        this.views = [];
    },

    // handle the subviews
    // override to destroy jQuery plugin instances
    unstage: function () {
        if (!this.views) {
            return;
        }

        var i, l = this.views.length;

        for (i = 0; i < l; i = i + 1) {
            this.views[i].destroy();
        }
        this.views = [];
    },

    // override to setup jQuery plugin instances
    stage: function () {
    },

    // destroy the view
    destroy: function () {
        this.unstage();
        this.remove();
        this.off();

        if (this.collection) {
            this.collection.off(null, null, this);
        }
        if (this.model) {
            this.model.off(null, null, this);
        }
    }
});

Die Aktualisierung meines vorherigen Beispiels, um den Zeilen ein ziehbares Verhalten zu geben, würde folgendermaßen aussehen:

var ItemView = BaseView.extend({
    stage: function () {
        this.$el.draggable({
            revert: "invalid",
            helper: "clone"
        });
    },

    unstage: function () {
        this.$el.draggable('destroy');
        BaseView.prototype.unstage.call(this);
    }
});

var ListView = BaseView.extend({

    render: function () {
       //same as before

        this.unstage();
        this.collection.each(function (model) {
            var subview = new ItemView({
                el: $table.find("#" + model.cid),
                model: model
            });
            subview.stage();
            this.views.push(subview);
        }, this);
        this.stage();

        this.$el.empty();
        this.$el.append($table);

        return this;
    }
});

http://jsfiddle.net/nikoshr/yL7g6/

Das Zerstören der Stammansicht durchläuft die Hierarchie der Ansichten und führt die erforderlichen Bereinigungen durch.

NB: Entschuldigung für den JS-Code, ich bin nicht genug vertraut mit Coffeescript, um genaue Snippets zu liefern.

https://src-bin.com

In den letzten sechs Monaten habe ich mit Backbone gearbeitet. In den ersten zwei Monaten wurde herumgespielt, gelernt und herausgefunden, wie ich meinen Code darum strukturieren möchte. In den nächsten 4 Monaten wurde eine produktionstaugliche Anwendung fertiggestellt. Versteh mich nicht falsch, Backbone hat mich vor dem tausenden von Fehlern auf der Client-Seite bewahrt, die früher der Standard waren, aber es ermöglichte mir, grandiosere Dinge in kürzerer Zeit zu erledigen, was einen völlig neuen Stapel von Problemen eröffnete. Für all die Fragen, die ich hier anspreche, gibt es einfache Lösungen, die sich wie Hacks anfühlen oder sich einfach falsch anfühlen. Ich verspreche 300 Punkte Kopfgeld für eine tolle Lösung. Hier geht:

  1. Wird geladen - Für unseren Anwendungsfall (ein Admin-Panel) ist die pessimistische Synchronisierung schlecht. Für einige Dinge muss ich Dinge auf dem Server validieren, bevor ich sie akzeptiere. Wir starteten, bevor das Ereignis 'sync' in Backbone zusammengeführt wurde.

und wir haben diesen kleinen Code verwendet, um das Ladeereignis nachzuahmen:

window.old_sync = Backbone.sync

# Add a loading event to backbone.sync
Backbone.sync = (method, model, options) ->
  old_sync(method, model, options)
  model.trigger("loading")

Groß. Es funktioniert wie erwartet, aber es fühlt sich nicht richtig an. Wir binden dieses Ereignis an alle relevanten Ansichten und zeigen ein Ladesymbol an, bis wir von diesem Modell ein Erfolgs- oder Fehlerereignis erhalten. Gibt es einen besseren, gesünderen Weg, dies zu tun?

Jetzt für die Harten:

  1. Zu viele Dinge machen sich zu viel - Lassen Sie uns sagen, unsere Anwendung haben Registerkarten. Jede Registerkarte steuert eine Sammlung. Auf der linken Seite erhalten Sie die Sammlung. Sie klicken auf ein Modell, um es in der Mitte zu bearbeiten. Sie ändern seinen Namen und drücken die Tabulatortaste, um zum nächsten Formularelement zu gelangen. Jetzt ist Ihre App eine "Echtzeit etwas", die den Unterschied bemerkt, Validierungen durchführt und die Änderung automatisch mit dem Server synchronisiert, keine Speicherschaltfläche erforderlich! Großartig, aber H2 am Anfang des Formulars ist der gleiche Name wie in der Eingabe - Sie müssen es aktualisieren. Oh, und Sie müssen den Namen auf der Liste auf die Seite aktualisieren. OH, und die Liste sortiert sich nach Namen!

Hier ein weiteres Beispiel: Sie möchten ein neues Objekt in der Sammlung erstellen. Sie drücken die Schaltfläche "Neu" und Sie beginnen mit dem Ausfüllen des Formulars. Fügen Sie den Artikel sofort zur Sammlung hinzu? Aber was passiert, wenn Sie beschlossen haben, es zu verwerfen? Oder wenn Sie die gesamte Sammlung auf einem anderen Tab speichern? Und es gibt einen Datei-Upload - Sie müssen das Modell speichern und synchronisieren, bevor Sie mit dem Hochladen der Datei beginnen können (damit Sie die Datei an das Modell anhängen können). So beginnt alles bei Erschütterungen zu rendern: Man speichert das Modell und die Liste und das Formular rendert sich selbst wieder - es ist jetzt synchronisiert, also bekommt man einen neuen Löschbutton, und es wird in der Liste angezeigt - aber jetzt ist der Datei-Upload fertig hochgeladen, also alles Startet das Rendern erneut.

Fügen Sie Subviews hinzu und alles fängt an, wie ein Fellini-Film auszusehen.

  1. Es sind Subviews ganz unten - Hier ist ein guter Artikel über dieses Zeug . Ich konnte aus der Liebe zu allem, was heilig ist, keine korrekte Methode finden, um jQuery-Plugins oder DOM-Ereignisse an jede Ansicht anzuhängen, die Unteransichten hat. Die Hölle kommt prompt. QuickInfos hören, dass ein Rendering lange dauert und sich herumtreibt, Subviews werden zombieartig oder reagieren nicht. Dies sind die Hauptprobleme, da hier die eigentlichen Bugs stehen, aber ich habe immer noch keine allumfassende Lösung.

  2. Flackern - Rendern ist schnell. In der Tat ist es so schnell, dass mein Bildschirm aussieht, als hätte es einen Anfall. Manchmal müssen Bilder wieder geladen werden (mit einem anderen Serveraufruf!), So dass der HTML-Code minimiert und dann wieder abrupt maximiert wird - eine CSS-Breite + Höhe für dieses Element wird das beheben. Manchmal können wir dies mit einem FadeIn und einem FadeOut lösen - was beim Schreiben schmerzhaft ist, da wir manchmal eine Ansicht wiederverwenden und manchmal neu erstellen.

TL; DR - Ich habe Probleme mit Ansichten und Unteransichten in Backbone - Es rendert zu oft, es flackert, wenn es rendert, Subviews lösen meine DOM-Ereignisse und essen meine Gehirne.

Vielen Dank!

Weitere Details: BackboneJS mit dem Ruby on Rails Gem. Vorlagen mit UnderscoreJS-Vorlagen.


Answer #1

Wird geladen...

Ich bin ein großer Fan von eifrigem Laden. Alle meine Serveraufrufe sind JSON-Antworten, es ist also keine große Sache, sie häufiger zu machen. Ich aktualisiere eine Sammlung normalerweise jedes Mal, wenn sie von einer Ansicht benötigt wird.

Mein liebster Weg zu eifriger Belastung ist die Verwendung von Backbone-relational . Wenn ich meine App hierarchisch organisiere. Bedenken Sie:

Organization model
|--> Event model
|--> News model
   |--> Comment model

Wenn also ein Benutzer eine organization anzeigt, kann ich die events und news dieser Organisation eifrig laden. Und wenn ein Benutzer einen news anzeigt, möchte ich die comments dieses Artikels laden.

Backbone-relational bietet eine großartige Schnittstelle für die Abfrage verwandter Datensätze vom Server.

Zu viele Dinge machen sich zu sehr ...

Backbone-relational hilft auch hier! Backbone-relational bietet einen globalen Plattenspeicher, der sich als sehr nützlich erweist. Auf diese Weise können Sie IDs weitergeben und das gleiche Modell an anderer Stelle abrufen. Wenn Sie es an einem Ort aktualisieren, ist es in einem anderen verfügbar.

a_model_instance = Model.findOrCreate({id: 1})

Ein anderes Werkzeug ist hier Backbone.ModelBinder . Backbone.ModelBinder können Sie Ihre Vorlagen erstellen und vergessen, Änderungen an den Ansichten anzuhängen. Also in Ihrem Beispiel des Sammelns von Informationen und zeigen Sie es in der Kopfzeile, nur Backbone.ModelBinder zu BEIDE dieser Elemente zu sagen, und bei der Eingabe change , wird Ihr Modell aktualisiert und auf Modellwechsel Sie werden aktualisiert, so dass jetzt die Überschrift wird Aktualisiert.

Es sind Subviews ganz unten ... Subviews werden zombieartig oder reagieren nicht ...

Ich mag Backbone.Marionette . Es behandelt einen Großteil der Bereinigung für Sie und fügt einen onShow Rückruf hinzu, der beim temporären Entfernen von Ansichten aus dem DOM nützlich sein kann.

Dies erleichtert auch das Anhängen von jQuery-Plugins. Die Methode onShow wird aufgerufen, nachdem die Ansicht gerendert und dem DOM hinzugefügt wurde, damit der jQuery-Plug-in-Code ordnungsgemäß funktionieren kann.

Es bietet auch einige coole Ansichtsvorlagen wie CollectionView , die eine Sammlung und ihre Unteransichten verwalten.

Flackern

Leider habe ich nicht viel Erfahrung damit, aber Sie könnten versuchen, die Bilder auch vorher zu laden. Rendere sie in einer versteckten Ansicht und bringe sie dann nach vorne.





coffeescript