ReactJS - Est-ce que le rendu est appelé chaque fois que «setState» est appelé?

503

React restitue-t-il tous les composants et sous-composants à chaque setStateappel?

Si oui, pourquoi? Je pensais que l'idée était que React rendait aussi peu que nécessaire - lorsque l'état changeait.

Dans l'exemple simple suivant, les deux classes s'affichent à nouveau lorsque vous cliquez sur le texte, malgré le fait que l'état ne change pas lors des clics suivants, car le gestionnaire onClick définit toujours statela même valeur:

this.setState({'test':'me'});

Je m'attendais à ce que les rendus ne se produisent que si les statedonnées avaient changé.

Voici le code de l'exemple, en tant que JS Fiddle et extrait de code intégré:

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

[1]: http://jsfiddle.net/fp2tncmb/2/
Brad Parks
la source
J'ai eu le même problème, je ne connais pas la solution exacte. Mais j'avais nettoyé les codes indésirables du composant sur lequel il avait commencé à fonctionner comme d'habitude.
Jaison
Je voudrais souligner que dans votre exemple - parce que la façon dont votre élément est conçu ne repose pas uniquement sur un état unique - appeler setState()même avec des données factices entraîne un rendu différent de l'élément, je dirais donc oui. Absolument, il devrait essayer de restituer votre objet lorsque quelque chose a changé, sinon votre démo - en supposant que c'était le comportement prévu - ne fonctionnerait pas!
Tadhg McDonald-Jensen du
Vous avez peut-être raison @ TadhgMcDonald-Jensen - mais d'après ce que je comprends, React l'aurait rendu la première fois (puisque l'état passe de rien à quelque chose cette première fois), puis n'a jamais eu à rendre à nouveau. Bien sûr, j'avais tort - car il semble que React vous oblige à écrire votre propre shouldComponentUpdateméthode, dont j'ai supposé qu'une version simple devait déjà être incluse dans React lui-même. On dirait que la version par défaut incluse dans react renvoie simplement true- ce qui force le composant à être rendu à chaque fois.
Brad Parks
Oui, mais il ne doit être rendu que dans le DOM virtuel, puis il ne change le DOM réel que si le composant est rendu différemment. Les mises à jour du DOM virtuel sont généralement négligeables (au moins par rapport à la modification de choses sur l'écran réel), donc appeler render à chaque fois qu'il pourrait avoir besoin de mettre à jour puis voir qu'aucun changement n'est arrivé n'est pas très cher et plus sûr que de supposer qu'il devrait rendre le même.
Tadhg McDonald-Jensen

Réponses:

570

React restitue-t-il tous les composants et sous-composants chaque fois que setState est appelé?

Par défaut - oui.

Il existe une méthode booléenne shouldComponentUpdate (object nextProps, object nextState) , chaque composant a cette méthode et il est responsable de déterminer "la mise à jour du composant (exécuter la fonction de rendu )?" chaque fois que vous changez d' état ou passez de nouveaux accessoires du composant parent.

Vous pouvez écrire votre propre implémentation de la méthode shouldComponentUpdate pour votre composant, mais l'implémentation par défaut retourne toujours true - ce qui signifie toujours réexécuter la fonction de rendu.

Citation de documents officiels http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

Par défaut, shouldComponentUpdate renvoie toujours true pour éviter les bogues subtils lorsque l'état est muté sur place, mais si vous faites attention à toujours traiter l'état comme immuable et à lire uniquement les accessoires et l'état dans render (), alors vous pouvez remplacer shouldComponentUpdate avec une implémentation qui compare les anciens accessoires et l'état à leurs remplacements.

Prochaine partie de votre question:

Si oui, pourquoi? Je pensais que l'idée était que React rendait aussi peu que nécessaire - lorsque l'état a changé.

Il y a deux étapes de ce que nous pouvons appeler le «rendu»:

  1. Rendu DOM virtuel: lorsque la méthode de rendu est appelée, elle retourne une nouvelle structure de dom virtuel du composant. Comme je l'ai mentionné précédemment, cette méthode de rendu est toujours appelée lorsque vous appelez setState () , car shouldComponentUpdate renvoie toujours true par défaut. Donc, par défaut, il n'y a pas d'optimisation ici dans React.

  2. Rendu DOM natif: React ne modifie les nœuds DOM réels dans votre navigateur que s'ils ont été modifiés dans le DOM virtuel et aussi peu que nécessaire - c'est la grande fonctionnalité de React qui optimise la vraie mutation DOM et rend React rapide.

Petr
la source
47
Grande explication! Et la seule chose qui fait vraiment briller React dans ce cas est qu'il ne change pas le DOM réel à moins que le DOM virtuel n'ait été changé. Donc, si ma fonction de rendu renvoie la même chose 2 fois de suite, le vrai DOM ne change pas du tout ... Merci!
Brad Parks
1
Je pense que @tungd a raison - voir sa réponse ci-dessous. Il s'agit également d'une relation parent-enfant entre les composants. Par exemple, si TimeInChild est un frère de Main, son render () ne sera pas appelé inutilement.
gvlax
2
Si votre état contient un objet javascript relativement volumineux (~ 1k propriétés totales), qui est rendu sous la forme d'une grande arborescence de composants (~ 100 au total) ... devez-vous laisser les fonctions de rendu construire le dom virtuel, ou devriez-vous, avant de définir l'état, comparer le nouvel état à l'ancien manuellement et appeler uniquement setStatesi vous détectez qu'il y a une différence? Si c'est le cas, comment le faire au mieux - comparer les chaînes json, construire et comparer les hachages d'objets, ...?
Vincent Sels
1
@Petr, mais même si react reconstruit le dom virtuel, si l'ancien dom virtuel est le même que le nouveau dom virtuel, le dom du navigateur ne sera pas touché, n'est-ce pas?
Jaskey
3
Essayez également d'utiliser React.PureComponent ( reactjs.org/docs/react-api.html#reactpurecomponent ). Il ne se met à jour (restitue) que si l'état ou les accessoires du composant ont réellement changé. Attention, cependant, la comparaison est superficielle.
débatteur le
105

Non, React ne restitue pas tout lorsque l'état change.

  • Chaque fois qu'un composant est sale (son état a changé), ce composant et ses enfants sont rendus de nouveau. Dans une certaine mesure, cela consiste à restituer le moins possible. Le seul moment où le rendu n'est pas appelé est lorsqu'une branche est déplacée vers une autre racine, où théoriquement nous n'avons pas besoin de restituer quoi que ce soit. Dans votre exemple, TimeInChildest un composant enfant de Main, il est donc également rendu à nouveau lorsque l'état des Mainmodifications.

  • React ne compare pas les données d'état. Quand setStateest appelé, il marque le composant comme sale (ce qui signifie qu'il doit être restitué). La chose importante à noter est que bien que la renderméthode du composant soit appelée, le vrai DOM n'est mis à jour que si la sortie est différente de l'arborescence DOM actuelle (c'est-à-dire la différence entre l'arborescence DOM virtuelle et l'arborescence DOM du document). Dans votre exemple, même si les statedonnées n'ont pas changé, l'heure de la dernière modification a changé, ce qui rend le DOM virtuel différent du DOM du document, d'où la raison pour laquelle le HTML est mis à jour.

tungd
la source
Oui, c'est la bonne réponse. Comme expérience, modifiez la dernière ligne en tant que React.renderComponent (<div> <Main /> <TimeInChild /> </div>, document.body) ; et supprimez <TimeInChild /> du corps de render () du composant Main . Le render () de TimeInChild ne sera pas appelé par défaut car il n'est plus un enfant de Main .
gvlax
Merci, des trucs comme ça sont un peu délicats, c'est pourquoi les auteurs de React ont recommandé que la render()méthode soit "pure" - indépendante de l'état extérieur.
tungd
3
@tungd, qu'est-ce que cela signifie some branch is moved to another root? Comment appelez-vous branch? Comment appelez-vous root?
Vert
2
Je préfère vraiment la réponse marquée comme la bonne. Je comprends votre interprétation du «rendu» du côté natif, «réel» ... mais si vous le regardez du côté réactif-natif, vous devez dire qu'il se rend à nouveau. Heureusement, il est assez intelligent pour déterminer ce qui a vraiment changé et ne mettre à jour que ces choses. Cette réponse peut être déroutante pour les nouveaux utilisateurs, d'abord vous dites non, puis vous expliquez que les choses sont rendues ...
WiRa
1
@tungd pouvez-vous expliquer la question de Greenwhat does it mean some branch is moved to another root? What do you call branch? What do you call root?
madhu131313
7

Même si cela est indiqué dans de nombreuses autres réponses ici, le composant doit soit:

  • implémenter shouldComponentUpdatepour rendre uniquement lorsque l'état ou les propriétés changent

  • passer à l'extension d'un PureComponent , qui implémente déjà une shouldComponentUpdateméthode en interne pour les comparaisons superficielles.

Voici un exemple d'utilisation shouldComponentUpdate, qui ne fonctionne que pour ce cas d'utilisation simple et à des fins de démonstration. Lorsque cette option est utilisée, le composant ne se restitue plus à chaque clic et est rendu lors de son premier affichage et après avoir été cliqué une fois.

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    shouldComponentUpdate: function(nextProps, nextState) {
      if (this.state == null)
        return true;
  
      if (this.state.test == nextState.test)
        return false;
        
      return true;
  },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

Brad Parks
la source
6

Oui. Il appelle la méthode render () chaque fois que nous appelons setState à la place lorsque "shouldComponentUpdate" renvoie false.

lakmal_sathyajith
la source
7
Veuillez être plus détaillé
Karthick Ramesh
3

Une autre raison de la "perte de mise à jour" peut être la suivante:

  • Si le getDerivedStateFromProps statique est défini, il est réexécuté dans chaque processus de mise à jour conformément à la documentation officielle https://reactjs.org/docs/react-component.html#updating .
  • donc si cette valeur d'état provient des accessoires au début, elle est écrasée dans chaque mise à jour.

Si c'est le problème, U peut éviter de définir l'état lors de la mise à jour, vous devez vérifier la valeur du paramètre d'état comme ceci

static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
   return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}

Une autre solution consiste à ajouter une propriété initialisée à l'état et à la configurer la première fois (si l'état est initialisé à une valeur non nulle).

Zoltán Krizsán
la source
0

Pas tous les composants.

le statecomposant in ressemble à la source de la cascade d'état de l'ensemble de l'APP.

Ainsi, le changement se produit d'où l'appel de setState. L'arbre est rendersalors appelé de là. Si vous avez utilisé un composant pur, le rendersera ignoré.

Singhi John
la source