web-dev-qa-db-fra.com

Puis-je éviter forceUpdate () lorsque j'utilise React avec Backbone?

Facebook Reactencourage vous séparez l'état mutable (state) et immuable (props):

Essayez de conserver autant de vos composants que possible sans état. En procédant ainsi, vous isolerez l'état à son emplacement le plus logique et réduirez la redondance, ce qui facilitera le raisonnement sur votre application.

Lorsque l'état change, vous êtes censé appeler setState pour déclencher un diff DOM virtuel, ce qui ne provoquera une véritable mise à jour DOM que lorsque cela sera nécessaire.

Il y a c'est un moyen de déclencher la mise à jour DOM manuellement en appelant forceUpdate mais c'est découragé :

Normalement , vous devriez essayer d'éviter toutes les utilisations de forceUpdate() et lire uniquement à partir de this.props Et this.state dans render(). Cela rend votre application beaucoup plus simple et plus efficace.

Cependant, tous les exemples React + Backbone que j'ai vus ignorent ce conseil et stockent les modèles et les collections dans props et appellent forceUpdate:

Même l'exemple de React utilise forceUpdate:

Existe-t-il une meilleure façon, et quels avantages cela apporterait-il?

40
Dan Abramov

La réponse de Pete est excellente.

Les modèles de dorsale sont intrinsèquement mutants, ce qui (bien qu'il ne s'agisse pas d'un problème en soi) signifie que lors du rendu, vous n'aurez pas l'ancienne version du modèle à comparer. Cela rend plus difficile les optimisations intelligentes en définissant des méthodes shouldComponentUpdate aux endroits clés de vos composants. (Vous perdez également la possibilité de stocker facilement les anciennes versions de votre modèle pour d'autres raisons, comme implémentation de l'annulation .)

L'appel de forceUpdate ignore simplement shouldComponentUpdate et force le composant à se rendre à nouveau. Notez que l'appel de render est généralement bon marché, et React ne touchera toujours le DOM que si la sortie de render a changé, donc les problèmes de performances ne sont pas ici Cependant, si vous avez le choix d'utiliser des données immuables (y compris le passage d'objets de propriété de modèle brut à partir de toJSON() comme le suggère Pete), je le recommande vivement.

18
Sophie Alpert

Jusqu'à ce qu'il y ait une meilleure réponse, laissez-moi citationPete Hunt , un noyau React développeur:

La grande victoire avec les modèles Backbone a été la gestion de votre flux de données pour vous. Lorsque vous appelez set(), cela informait votre application que les données avaient changé. Avec React vous trouverez cela moins nécessaire car tout ce que vous avez à faire est d'informer le composant propriétaire de l'état via un rappel et React garantit que tous les enfants sont à jour. Donc, cette partie de l'épine dorsale est moins utile IMO (et les gens ont tendance à utiliser l'épine dorsale de cette façon avec React de toute façon).

Vous n'avez pas besoin de passer du JSON pur (bien que c'est ce que j'ai tendance à faire et cela fonctionne bien pour les modèles de données simples), mais vous verrez de nombreux avantages si vous gardez vos objets immuables.

Vous pouvez essayer cela en appelant simplement toJSON() sur vos modèles de base et en voyant comment vous l'aimez vs en passant les modèles.

(c'est moi qui souligne)

Fait intéressant, Backbone.React.Component est le seul exemple que j'ai trouvé qui utilise toJSON, mais pour une raison quelconque, utilise également setProps au lieu de setState ( ce qui est déconseillé aussi ).

Mise à jour

J'ai fait un mixin simple basé sur l'approche de Pete Hunt (pas de setProps, pas de forceUpdate):

define(function () {

  'use strict';

  var Backbone = require('backbone'),
      _ = require('underscore');

  var BackboneStateMixin = {
    getInitialState: function () {
      return this.getBackboneState(this.props);
    },

    componentDidMount: function () {
      if (!_.isFunction(this.getBackboneState)) {
        throw new Error('You must provide getBackboneState(props).');
      }

      this._bindBackboneEvents(this.props);
    },

    componentWillReceiveProps: function (newProps) {
      this._unbindBackboneEvents();
      this._bindBackboneEvents(newProps);
    },

    componentWillUnmount: function () {
      this._unbindBackboneEvents();
    },

    _updateBackboneState: function () {
      var state = this.getBackboneState(this.props);
      this.setState(state);
    },

    _bindBackboneEvents: function (props) {
      if (!_.isFunction(this.watchBackboneProps)) {
        return;
      }

      if (this._backboneListener) {
        throw new Error('Listener already exists.');
      }

      if (!props) {
        throw new Error('Passed props are empty');
      }

      var listener = _.extend({}, Backbone.Events),
          listenTo = _.partial(listener.listenTo.bind(listener), _, _, this._updateBackboneState);

      this.watchBackboneProps(props, listenTo);
      this._backboneListener = listener;
    },

    _unbindBackboneEvents: function () {
      if (!_.isFunction(this.watchBackboneProps)) {
        return;
      }

      if (!this._backboneListener) {
        throw new Error('Listener does not exist.');
      }

      this._backboneListener.stopListening();
      delete this._backboneListener;
    }
  };

  return BackboneStateMixin;

});

Peu importe le type de modèles ou de collections que vous possédez.

La convention est que les modèles de backbone vont dans props et leur JSON est automatiquement mis par mixin dans state. Vous devez remplacer getBackboneState(props) pour que cela fonctionne, et éventuellement watchBackboneProps pour indiquer au mixin quand appeler setState avec de nouvelles valeurs.

Exemple d'utilisation:

var InfoWidget = React.createClass({
  mixins: [BackboneStateMixin, PopoverMixin],

  propTypes: {
    stampModel: React.PropTypes.instanceOf(Stamp).isRequired
  },

  // Override getBackboneState to tell the mixin
  // HOW to transform Backbone props into JSON state

  getBackboneState: function (props) {
    var stampModel = props.stampModel,
        primaryZineModel = stampModel.getPrimaryZine();

    return {
      stamp: stampModel.toJSON(),
      toggleIsLiked: stampModel.toggleIsLiked.bind(stampModel),
      primaryZine: primaryZineModel && primaryZineModel.toJSON()
    };
  },

  // Optionally override watchBackboneProps to tell the mixin
  // WHEN to transform Backbone props into JSON state

  watchBackboneProps: function (props, listenTo) {
    listenTo(props.stampModel, 'change:unauth_like_count change:is_liked');
    listenTo(props.stampModel.get('zines'), 'all');
  },

  render: function () {
    // You can use Vanilla JSON values of this.state.stamp,
    // this.state.toggleIsLiked and this.state.primaryZine
    // or whatever you return from getBackboneState
    // without worrying they may point to old values
  }
}

Remarque: mixin nécessite Underscore 1.6.0+.

17
Dan Abramov

Je suis le développeur derrière Backbone.React.Component. La raison pour laquelle nous utilisons setProps est que cela est uniquement destiné à être appelé par le propriétaire du composant (le plus grand parent). Selon moi, les accessoires sont mieux utilisés pour les mises à jour réactives (et pour passer aux composants enfants) que l'état, mais si vous pouvez me signaler quelques raisons pour lesquelles l'état est meilleur, je serai heureux de commencer à évoluer vers ce changement.

Par exemple, j'ai parfois des composants qui délèguent à d'autres, où transferPropsTo est assez pratique. L'utilisation de l'état rend plus difficile la réalisation de cet objectif.

8
Magalhas