33 votes

Problèmes de rendu de BackboneJS

Pour les six derniers mois, j'ai travaillé avec la colonne vertébrale. Les deux premiers mois ont été de déconner, de l'apprentissage et de comprendre comment je veux la structure de mon code autour d'elle. Les 4 prochains mois, ont été pilonnant de production-ajustement de l'application. Ne m'obtenez pas le mal, de la colonne vertébrale m'a sauvé de milliers de lignes de gâchis de code côté client qui ont été la norme avant, mais il m'a permis de ne plus grandioses de choses en moins de temps, l'ouverture d'une nouvelle pile de problèmes. Pour toutes les questions que je soulève ici, il existe des solutions simples qui se sent comme hacks ou juste se sentir mal. Je promets de 300 points de bounty pour passer une excellente solution. Va ici:

  1. Chargement - Pour notre cas d'utilisation (un panneau d'administration) pessimiste de la synchronisation est mauvais. Pour certaines choses, j'ai besoin de valider des choses sur le serveur avant de les accepter. Nous avons commencé avant le "sync" de l'événement a été fusionnée dans la colonne vertébrale,

et nous avons utilisé ce petit code pour imiter le chargement de l'événement:

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")

Grand. Il fonctionne comme prévu, mais il ne semble pas correcte. Nous lier cet événement à tous les points de vue pertinents et d'afficher une icône de chargement jusqu'à ce que nous recevons une erreur ou de réussite de l'événement à partir de ce modèle. Est-il un meilleur, plus sain, façon de le faire?

Maintenant, pour les difficiles:

  1. Trop de choses se rendent trop , disons à notre demande, des onglets. Chaque onglet contrôle d'une collection. Sur le côté gauche, vous obtenez la collection. Vous cliquez sur un modèle pour commencer à le modifier au centre. Vous de changer son nom et appuyez sur la touche tab pour accéder au prochain élément de formulaire. Maintenant, votre application est un "temps réel quelque chose de quelque chose" que les avis de la différence, exécute les validations, et de synchroniser automatiquement les changements sur le serveur, pas de bouton enregistrer nécessaire! Grande, mais le H2 au début du formulaire est le même nom que dans l'entrée, vous devez le mettre à jour. Oh, et vous avez besoin de mettre à jour le nom de la liste sur le côté. OH, et la liste s'organise par les noms!

Voici un autre exemple: Vous voulez créer un nouvel élément dans la collection. Vous appuyez sur le bouton "nouveau" et vous commencer à remplir le formulaire. Avez-vous immédiatement ajouter l'élément à la collection? Mais qu'advient-il si vous avez décidé de la jeter? Ou si vous enregistrez l'ensemble de la collection sur un autre onglet? Et, il y a un upload de fichier - Vous besoin d'enregistrer et de synchroniser le modèle avant de pouvoir commencer à télécharger le fichier (de sorte que vous pouvez attacher le fichier du modèle). Donc, tout commence rendu dans les tremblements: Vous enregistrez le modèle et la liste et la forme se rend de nouveau - c'est maintenant synchronisés, de sorte que vous obtenez un nouveau bouton supprimer, et il montre dans la liste - mais maintenant, le téléchargement de fichier téléchargement terminé, de sorte que tout commence un nouveau rendu.

Ajouter des sous-vues pour le mix et tout commence à ressembler à un film de Fellini.

  1. C'est des sous-vues tout le chemin vers le bas - Voici un bon article sur ce genre de choses. Je ne pouvais pas, pour l'amour de tout ce qui est saint, trouver une bonne façon de joindre les plugins jQuery ou d'événements DOM à tout point de vue qui a des sous-vues. L'enfer s'ensuit rapidement. Les info-bulles entendre un rendu venir une longue et commencez à paniquer autour, des sous-vues deviennent comme des zombies ou ne répondent pas. C'est les principaux points de la douleur comme ici réelle de bugs, mais je n'ai pas encore globale de la solution.

  2. Le scintillement - Rendu est rapide. En fait, il est tellement rapide que mon écran ressemble, il a eu une crise. Parfois, il est des images qui a pour charger à nouveau (avec un autre serveur à l'appel!), si le html est réduit et puis maximise nouveau brusquement et un css largeur+hauteur de l'élément va résoudre ce problème. parfois, nous pouvons le résoudre avec un fadeIn et fadeOut - qui sont une douleur dans le cul à écrire, car nous sommes parfois la réutilisation d'une vue et parfois la création de nouveau.

TL;DR - je vais avoir des problèmes avec les vues et les sous-vues de la colonne vertébrale - Il rend une fois de trop, il clignote quand il rend, sous-vues détacher mon DOM événements et manger mon cerveau.

Merci!!!!

Plus de détails: BackboneJS avec le framework Ruby on Rails Gem. Des modèles à l'aide UnderscoreJS modèles.

17voto

nikoshr Points 17480

Le rendu partiel de vues

Afin de minimiser le rendu complet de votre hiérarchie DOM, vous pouvez définir des nœuds spéciaux dans votre DOM qui va refléter les mises à jour sur une propriété donnée.

Nous allons utiliser ce simple trait de Soulignement modèle, une liste de noms:

<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>

Avis de la classe model-<%= model.cid %>-name, ce sera notre point d'injection.

Nous pouvons alors définir une vue de la base (ou modifier l'épine Dorsale.La vue) pour remplir ces nœuds avec les valeurs appropriées lorsqu'elles sont mises à jour:

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

Le code permettrait de varier un peu pour accueillir un modèle au lieu d'une collection. Un Violon pour jouer avec http://jsfiddle.net/nikoshr/cfcDX/

En limitant les manipulations DOM

Déléguer le rendu de la sous-vues peuvent être coûteux, de leurs fragments de HTML doivent être insérés dans le DOM de la société mère. Jetez un oeil à cette jsperf test de comparaison de différentes méthodes de rendu

L'essentiel, c'est que la génération du HTML complète de la structure et de l'application des points de vue est beaucoup plus rapide que la construction de points de vue et des sous-vues et puis en cascade le rendu. Par exemple,

<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/

Notez que le jsperf montre que le modèle peut être divisé en subtemplates sans trop de peine, qui vous permettra de fournir un rendu partiel pour les lignes.

Sur une note connexe, ne fonctionnent pas sur les nœuds attachés à la DOM, cela va entraîner des remboursements. Créer un nouveau DOM ou détacher le nœud avant de la manipuler.

Écraser les zombies

Derick Bailey a écrit un excellent article sur le sujet de l'éradication de zombie vues

Fondamentalement, vous devez vous rappeler que lorsque vous jetez une vue, vous devez séparer tous les auditeurs et effectuer toutes les opérations de nettoyage supplémentaire voulez détruire le plugin jQuery instances. Ce que j'utilise est une combinaison de méthodes similaires à ce que Derick utilise dans la colonne vertébrale.Marionnette:

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

La mise à jour de mon précédent exemple à donner les lignes d'un déplaçable comportement devrait ressembler à ceci:

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/

Détruire la vue de la racine de la traversée de la hiérarchie de vues et d'effectuer les opérations de nettoyage.

NB: désolé pour le code JS, je ne suis pas assez familier avec Coffeescript, afin de fournir des extraits de code.

9voto

Arkady Points 689

Ok, dans l'ordre.. :)

  1. Chargement...

Dans le cas où vous souhaitez valider les données stockées sur le serveur, une bonne pratique de le faire sur le côté serveur. Si la validation sur le serveur échoue, le serveur doit envoyer pas de code HTTP 200, donc sauf metod de la colonne vertébrale.Modèle de déclencher d'erreur.

De l'autre côté, pour la validation des données de l'épine dorsale a pas été appliqués de valider la méthode. Je pense que le bon choix de mettre en œuvre et à utiliser. Mais gardez à l'esprit que valider est appelé avant de configurer et d'enregistrer, et si valider renvoie une erreur, configurer et d'enregistrer ne va pas continuer, et les attributs de modèle ne sera pas modifié. Échoué déclencher un évènement "erreur".

Une autre façon, quand nous appelons le silence set(avec {silencieux: true} param), nous devrions l'appeler méthode isValid manuellement pour valider les données.

  1. Trop de choses se rendent trop..

Vous devez séparer vos points de Vue, en vertu de leur logique. Bonnes pratiques pour la collecte est vue séparée pour chaque modèle. Dans ce cas, vous pourriez rendre chaque élément de façon indépendante. Et encore plus lorsque vous initalizing votre conteneur de vue de la collecte, vous pouvez associer n'importe quel événement de chaque modèle de la collection à la vue appropriée, et ils vont rendre automatiquement.

Grande, mais le H2 au début du formulaire est le même nom que dans la d'entrée, vous devez le mettre à jour. Oh, et vous avez besoin de mettre à jour le nom sur la liste sur le côté.

vous pouvez utiliser JQuery sur la méthode à mettre en œuvre de rappel qui envoyer la valeur à afficher. Exemple:

//Container view
init: function() {
    this.collection = new Backbone.Collection({
        url: 'http://mybestpage.com/collection'
    });
    this.collection.bind('change', this.render, this);
    this.collection.fetch();
},
render: function() {

    _.each(this.collection.models, function(model) {
         var newView = new myItemView({
              model: model,
              name: 'view' + model.id
         });
         this.$('#my-collection').append(newView.render().$el);
         view.on('viewEdit', this.displayValue);
    }, this);
},
...
displayValue: function(value) {
    //method 1
    this.displayView.setText(value); //we can create little inner view before, 
                                     //for text displaying. Сonvenient at times.
    this.displayView.render();
    //method 2
    $(this.el).find('#display').html(value);
}

//View from collection
myItemView = Backbone.View.extend({
events: {
    'click #edit': 'edit'
},
init: function(options) {
    this.name = options.name;
},
...
edit: function() {
    this.trigger('viewEdit', this.name, this);
}

OH, et la liste s'organise par les noms!

Vous pouvez utiliser de tri méthode pour la colonne vertébrale des collections. Mais (!) L'appel de trier les déclencheurs de la collection "reset" de l'événement. Pass {silencieux: true} pour éviter cela. Comment

Voici un autre exemple: Vous voulez créer un nouvel élément dans le la collection de...

Quand on appuie sur un bouton "Nouveau" nous avons besoin de créer un nouveau modèle, mais seulement quand .méthode save() déclenchera le succès, nous devons pousser ce modèle de la collection. Dans un autre cas, on peut afficher un message d'erreur. Bien sûr, nous n'avons pas de raisons d'ajouter un nouveau modèle de notre collection jusqu'à ce qu'il a été validé et enregistré sur le serveur.

  1. C'est des sous-vues tout en bas... des sous-vues deviennent comme des zombies ou ne répondent pas.

lorsque vous (ou un modèle) l'appel de méthode render, tous les éléments à l'intérieur il sera recréé. Donc, dans le cas où vous avez des sous-vues, vous devriez appeler subView.delegateEvents(subView.events); pour l'ensemble des sous-vues; Probablement cette méthode est peu d'astuce, mais ça fonctionne.

  1. Le scintillement..

À l'aide de vignettes pour les grandes et moyennes images minimiser le flou dans beaucoup de cas. Autre façon, vous pourriez séparer le rendu de la vue d'images et d'autres contenus.

Exemple:

var smartView = Backbone.View.extend({
  initialize: function(){
    this.model.on( "imageUpdate", this.imageUpdate, this );
    this.model.on( "contentUpdate", this.contentUpdate, this );
  },

  render: function(){
    this.$el.html(this.template(this.model.toJSON()));
  },

  imageUpdate: function(){
    this.$el.find('#image').attr('src', this.model.get('imageUrl'));
  },
  contentUpdate: function(){
    this.$el.find('#content').html(this.model.get('content'));
  }
})

J'espère que cela aide quelqu'un. Désolé pour les erreurs de grammaire, le cas échéant :)

2voto

nickaknudson Points 1560

Chargement...

Je suis un grand fan de chargement impatient. Tous mes appels au serveur sont des réponses JSON, donc ce n'est pas une affaire énorme pour les rendre plus souvent que les autres. J'ai l'habitude d'actualisation d'une collection à chaque fois que c'est nécessaire en vue.

Ma façon préférée de désireux de charge est d'utiliser la colonne vertébrale-relationnel. Si je organiser mon application, de manière hiérarchique. Réfléchissez à ceci:

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

Ainsi, lorsqu'un utilisateur consulte un organization je peux désireux de charge de l'organisation events et news. Et lorsqu'un utilisateur consulte un news article, j'ai hâte de la charge que l'article de l' comments.

Colonne vertébrale-relationnel dispose d'une interface d'interrogation des documents connexes à partir du serveur.

Trop de choses se rendent trop...

Colonne vertébrale-relationnel permet ici aussi! Colonne vertébrale-relationnel en propose une boutique qui s'avère être très utile. De cette façon, vous pouvez passer autour de l'IDs et de récupérer le même modèle ailleurs. Si vous mettez à jour en un seul endroit, il est disponible dans un autre.

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

Un autre outil est l' épine Dorsale.ModelBinder. De la colonne vertébrale.ModelBinder vous permet de construire vos modèles et d'oublier à propos de l'attachement pour afficher les modifications. Donc dans votre exemple de la collecte de l'information et de la montrer dans l'en-tête, juste dire épine Dorsale.ModelBinder à regarder ces DEUX éléments, et sur l'entrée change, votre modèle sera mis à jour et sur le modèle d' change vous sera mis à jour, de sorte que maintenant l'en-tête sera mise à jour.

C'est des sous-vues tout en bas... des sous-vues deviennent comme des zombies ou de ne pas répondre...

J'aime vraiment la colonne vertébrale.Marionnette. Il gère beaucoup de nettoyage pour vous et ajoute un onShow de rappel qui peut être utile lors de supprimer temporairement la vue depuis le DOM.

Cela permet également de faciliter la connexion de plugins jQuery. Le onShow méthode est appelée après la vue est rendue et a ajouté au DOM de sorte que le plugin jQuery code puisse fonctionner correctement.

Il fournit également une vue impressionnante de modèles tels que CollectionView qui fait un travail remarquable de la gestion de la collection et de ses sous-vues.

Le scintillement

Malheureusement je n'ai pas beaucoup d'expérience avec cela, mais vous pourriez essayer de pré-chargement des images. Rendre dans un caché de la vue et de les mettre ensuite en avant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X