React conserve-t-il l'ordre des mises à jour d'état?

140

Je sais que React peut effectuer des mises à jour d'état de manière asynchrone et par lots pour l'optimisation des performances. Par conséquent, vous ne pouvez jamais faire confiance à l'état à mettre à jour après avoir appelé setState. Mais pouvez - vous faire confiance Réagir à mettre à jour l'état dans le même ordre que l' setStateon appelle pour

  1. le même composant?
  2. différents composants?

Pensez à cliquer sur le bouton dans les exemples suivants:

1. Y a-t-il jamais une possibilité que a soit faux et b soit vrai pour:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Y a-t-il jamais une possibilité que a soit faux et b soit vrai pour:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Gardez à l'esprit que ce sont des simplifications extrêmes de mon cas d'utilisation. Je me rends compte que je peux faire cela différemment, par exemple mettre à jour les deux paramètres d'état en même temps dans l'exemple 1, ainsi qu'exécuter la deuxième mise à jour d'état dans un rappel de la première mise à jour d'état dans l'exemple 2. Cependant, ce n'est pas ma question, et je ne suis intéressé que s'il existe une manière bien définie dont React effectue ces mises à jour d'état, rien d'autre.

Toute réponse appuyée par de la documentation est grandement appréciée.

darksmurf
la source
3
cela ne semble pas une question insensée, vous pouvez également poser cette question sur les problèmes github de la page de réaction, dan abramov est généralement très utile là-bas. Quand j'avais des questions aussi délicates, je posais et il répondait. Le problème est que ce genre de problèmes ne sont pas partagés publiquement comme dans les documents officiels (afin que d'autres puissent également y accéder facilement). Je pense également que les documents officiels de React ne couvrent pas
suffisamment
Par exemple, prenez ceci: github.com/facebook/react/issues/11793 , je pense que les éléments abordés dans ce numéro seraient utiles pour de nombreux développeurs, mais ces éléments ne figurent pas dans la documentation officielle, car les gens de FB considèrent que cela est avancé. Il en va de même pour d'autres choses peut-être. Je pense qu'un article officiel intitulé quelque chose comme «la gestion de l'État en réaction en profondeur» ou «les pièges de la gestion de l'État» qui explorent tous les cas secondaires de la gestion de l'État comme dans votre question ne serait pas mauvais. peut-être pouvons-nous pousser les développeurs FB à étendre la documentation avec ce genre de choses :)
giorgim
Thre est un lien vers un excellent article sur le support dans ma question. Il devrait couvrir 95% des cas d'utilisation des États. :)
Michal
2
@Michal mais cet article ne répond toujours pas à cette question IMHO
giorgim

Réponses:

336

Je travaille sur React.

TLDR:

Mais pouvez-vous faire confiance à React pour mettre à jour l'état dans le même ordre que setState est appelé

  • le même composant?

Oui.

  • différents composants?

Oui.

L' ordre des mises à jour est toujours respecté. Que vous voyiez un état intermédiaire "entre" eux ou non dépend de si vous êtes dans un lot ou non.

Actuellement (React 16 et versions antérieures), seules les mises à jour à l'intérieur des gestionnaires d'événements React sont regroupées par défaut . Il existe une API instable pour forcer le traitement par lots en dehors des gestionnaires d'événements dans de rares cas où vous en avez besoin.

Dans les versions futures (probablement React 17 et versions ultérieures), React regroupera toutes les mises à jour par défaut afin que vous n'ayez pas à y penser. Comme toujours, nous annoncerons tout changement à ce sujet sur le blog React et dans les notes de publication.


La clé pour comprendre cela est que quel que soit le nombre d' setState()appels dans le nombre de composants que vous effectuez dans un gestionnaire d'événements React , ils ne produiront qu'un seul rendu à la fin de l'événement . Ceci est crucial pour de bonnes performances dans les applications volumineuses, car si Childet à Parentchaque appel setState()lors de la gestion d'un événement de clic, vous ne souhaitez pas effectuer le rendu Childdeux fois.

Dans vos deux exemples, les setState()appels se produisent dans un gestionnaire d'événements React. Par conséquent, ils sont toujours vidés ensemble à la fin de l'événement (et vous ne voyez pas l'état intermédiaire).

Les mises à jour sont toujours fusionnées de manière superficielle dans l'ordre dans lequel elles se produisent . Donc, si la première mise à jour est {a: 10}, la deuxième est {b: 20}et la troisième est {a: 30}, l'état rendu sera {a: 30, b: 20}. La mise à jour la plus récente de la même clé d'état (par exemple, comme adans mon exemple) "gagne" toujours.

L' this.stateobjet est mis à jour lorsque nous rendons à nouveau l'interface utilisateur à la fin du lot. Donc, si vous avez besoin de mettre à jour l'état en fonction d'un état précédent (tel que l'incrémentation d'un compteur), vous devez utiliser la setState(fn)version fonctionnelle qui vous donne l'état précédent, au lieu de lire à partir de this.state. Si vous êtes curieux de connaître le raisonnement, je l'ai expliqué en détail dans ce commentaire .


Dans votre exemple, nous ne verrons pas "l'état intermédiaire" car nous sommes dans un gestionnaire d'événements React où le traitement par lots est activé (car React "sait" quand nous quittons cet événement).

Cependant, tant dans React 16 que dans les versions antérieures, il n'y a pas encore de traitement par lots par défaut en dehors des gestionnaires d'événements React . Donc, si dans votre exemple nous avions un gestionnaire de réponse AJAX à la place de handleClick, chacun setState()serait traité immédiatement comme il se produit. Dans ce cas, oui, vous voulez voir un état intermédiaire:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Nous nous rendons compte qu'il est peu pratique que le comportement soit différent selon que vous soyez dans un gestionnaire d'événements ou non . Cela changera dans une future version de React qui regroupera toutes les mises à jour par défaut (et fournira une API opt-in pour effacer les modifications de manière synchrone). Jusqu'à ce que nous changions le comportement par défaut (potentiellement dans React 17), il existe une API que vous pouvez utiliser pour forcer le traitement par lots :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Les gestionnaires d'événements React en interne sont tous encapsulés, unstable_batchedUpdatesc'est pourquoi ils sont regroupés par défaut. Notez que l'encapsulation d'une mise à jour unstable_batchedUpdatesdeux fois n'a aucun effet. Les mises à jour sont vidées lorsque nous quittons l' unstable_batchedUpdatesappel le plus externe .

Cette API est "instable" dans le sens où nous la supprimerons lorsque le traitement par lots est déjà activé par défaut. Cependant, nous ne le supprimerons pas dans une version mineure, vous pouvez donc vous y fier en toute sécurité jusqu'à React 17 si vous devez forcer le traitement par lots dans certains cas en dehors des gestionnaires d'événements React.


Pour résumer, c'est un sujet déroutant car React ne fait par défaut que des lots à l'intérieur des gestionnaires d'événements. Cela changera dans les versions futures, et le comportement sera alors plus simple. Mais la solution n'est pas de mettre moins de lots en lots , c'est d'en lots plus par défaut. C'est ce que nous allons faire.

Dan Abramov
la source
1
Une façon de "toujours obtenir le bon ordre" est de créer un objet temporaire, d'attribuer les différentes valeurs (par exemple obj.a = true; obj.b = true) et ensuite de faire this.setState(obj). Ceci est sûr, que vous soyez ou non dans un gestionnaire d'événements. Cela pourrait être une bonne astuce si vous vous retrouvez souvent à faire l'erreur de définir l'état plusieurs fois en dehors des gestionnaires d'événements.
Chris
Nous ne pouvons donc pas nous fier au traitement par lots pour se limiter à un seul gestionnaire d'événements - comme vous l'avez clairement indiqué, du moins parce que ce ne sera pas le cas de sitôt. Ensuite, nous sommes censés utiliser setState avec la fonction de mise à jour pour accéder à l'état le plus récent, non? Mais que se passe-t-il si je dois utiliser un filtre state.filter pour créer un XHR pour lire certaines données, puis les mettre en état? On dirait que je vais devoir mettre un XHR avec un rappel différé (et donc un effet secondaire) dans un programme de mise à jour. Est-ce alors considéré comme une pratique exemplaire?
Maksim Gumerov
1
Et en passant, cela signifie également que nous ne devons pas du tout lire cet état; la seule manière raisonnable de lire un état.X est de le lire dans la fonction de mise à jour, à partir de son argument. Et, écrire à this.state est également dangereux. Alors pourquoi autoriser l'accès à this.state? Celles-ci devraient peut-être être des questions indépendantes, mais surtout j'essaie simplement de comprendre si j'ai bien compris l'explication.
Maksim Gumerov
10
cette réponse doit être ajoutée à la documentation de reactjs.org
Deen John
2
Pourriez-vous s'il vous plaît clarifier dans cet article si le "gestionnaire d'événements React" inclut componentDidUpdateet d'autres rappels de cycle de vie à partir de React 16? Merci d'avance!
Ivan le
6

C'est en fait une question assez intéressante mais la réponse ne devrait pas être trop compliquée. Il y a cet excellent article sur le médium qui a une réponse.

1) Si vous faites cela

this.setState({ a: true });
this.setState({ b: true });

Je ne pense pas qu'il y aura une situation où asera trueet bsera à falsecause du traitement par lots .

Cependant, si bdépend de,a il peut effectivement y avoir une situation où vous n'obtiendrez pas l'état attendu.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Une fois que tous les appels ci-dessus sont traités, il y this.state.valueaura 1, pas 3 comme vous vous attendez.

Ceci est mentionné dans l'article: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Cela nous donnera this.state.value === 3

Michal
la source
Que faire si this.state.valueest mis à jour à la fois dans les gestionnaires d'événements (où setStateest groupé) et les rappels AJAX (où setStaten'est pas groupé). Dans les gestionnaires d'événements, j'utiliserais le updater functionpour toujours être sûr de mettre à jour l'état en utilisant l'état actuellement mis à jour fourni par la fonction. Dois-je utiliser setStateavec une fonction de mise à jour dans le code du rappel AJAX même si je sais qu'il n'est pas groupé? Pourriez-vous s'il vous plaît clarifier l'utilisation de setStatedans un rappel AJAX avec ou sans utiliser une fonction de mise à jour? Je vous remercie!
tonix
@Michal, salut Michal voulait juste poser une simple question, est-il vrai que si nous avons this.setState ({value: 0}); this.setState ({valeur: this.state.value + 1}); le premier setState sera ignoré et seul le second setState sera exécuté?
Dickens
@Dickens Je crois que les deux setStateseraient exécutés mais le dernier gagnerait.
Michal
3

Plusieurs appels au cours du même cycle peuvent être groupés ensemble. Par exemple, si vous essayez d'incrémenter une quantité d'article plus d'une fois dans le même cycle, cela se traduira par l'équivalent de:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html

Mosè Raguzzini
la source
3

comme dans doc

setState () met en file d'attente les modifications apportées à l'état du composant et indique à React que ce composant et ses enfants doivent être rendus avec l'état mis à jour. Il s'agit de la principale méthode que vous utilisez pour mettre à jour l'interface utilisateur en réponse aux gestionnaires d'événements et aux réponses du serveur.

il préformera le changement comme dans la file d'attente ( FIFO : First In First Out) le premier appel sera le premier à effectuer

Ali
la source
salut Ali, je voulais juste poser une simple question, est-il vrai que si nous avons this.setState ({value: 0}); this.setState ({valeur: this.state.value + 1}); le premier setState sera ignoré et seul le second setState sera exécuté?
Dickens