web-dev-qa-db-fra.com

Est-ce que React conserve l'ordre des mises à jour d'état?

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

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

Pensez à cliquer sur le bouton dans les exemples suivants:

1. Existe-t-il une possibilité que a est faux et b est 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. Existe-t-il une possibilité que a est faux et b est 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 le faire différemment, par exemple mettre à jour les deux paramètres d'état en même temps dans l'exemple 1, ainsi que d'effectuer 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 m'intéresse que s'il existe une manière bien définie que React effectue ces mises à jour d'état, rien d'autre.

Toute réponse étayée par une documentation est grandement appréciée.

106
darksmurf

Je travaille sur React.

TLDR:

Mais pouvez-vous faire confiance à React pour mettre à jour l'état dans le même ordre que celui où 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" 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 mises en lot par défaut . Il existe une API instable pour forcer le traitement par lots en dehors des gestionnaires d'événements dans les rares cas où vous en avez besoin.

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


La clé pour comprendre cela est que quel que soit le nombre de setState() appelle le nombre de composants que vous exécutez dans un gestionnaire d'événement React , ils ne produiront qu’un seul re-rendu à la fin de l’événement . Ceci est crucial pour de bonnes performances dans les grandes applications car si Child et Parent chaque appel setState() lors de la gestion d'un événement de clic, vous ne souhaitez pas rendre à nouveau le Child deux fois.

Dans vos deux exemples, les appels setState() ont lieu 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 peu fusionnées dans l'ordre dans lequel elles se produisent . Ainsi, si la première mise à jour est {a: 10}, la deuxième est {b: 20} et que la troisième est {a: 30}, l'état de rendu sera {a: 30, b: 20}. La mise à jour la plus récente de la même clé d’état (par exemple, comme a dans mon exemple) "gagne toujours".

L'objet this.state est mis à jour lorsque nous rendons à nouveau l'interface utilisateur à la fin du lot. Ainsi, si vous devez mettre à jour un état basé sur un état précédent (par exemple, en incrémentant un compteur), vous devez utiliser la version fonctionnelle setState(fn) qui vous donne l'état précédent, au lieu de lire 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 verrions pas "l'état intermédiaire" car nous sommes à l'intérieur d'un gestionnaire d'événements React où le traitement par lots est activé (car React "sait" quand nous quittons cet événement).

Cependant, dans React 16 et les versions antérieures , il n’existe pas encore de traitement par lots en dehors des gestionnaires d’événements React . Ainsi, si dans votre exemple, nous avions un gestionnaire de réponses AJAX au lieu de handleClick, chaque setState() serait immédiatement traitée en temps voulu. Dans ce cas, oui, vous verriez 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 gênant que le comportement soit différent selon que vous êtes dans un gestionnaire d'événements ou non . Cela changera dans une future version React qui regroupera par défaut toutes les mises à jour (et fournira une API d'adhésion pour vider 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
});

En interne, les gestionnaires d'événements React sont tous enveloppés dans unstable_batchedUpdates, raison pour laquelle ils sont mis en lot par défaut. Notez que le fait d'encapsuler une mise à jour dans unstable_batchedUpdates deux fois n'a aucun effet. Les mises à jour sont vidées lorsque nous sortons de l'appel unstable_batchedUpdates le plus à l'extérieur.

Cette API est "instable" en ce sens que 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, il s'agit d'un sujet déroutant, car React ne traite que les lots à l'intérieur des gestionnaires d'événements par défaut. Cela changera dans les versions futures et le comportement sera plus simple ensuite. Mais la solution n'est pas de lot moins , c'est lot plus par défaut. C'est ce que nous allons faire.

273
Dan Abramov

C'est en fait une question assez intéressante mais la réponse ne devrait pas être trop compliquée. Il y a ce grand article sur support qui a une réponse.

1) Si tu fais ça

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

Je ne pense pas qu'il y aura une situation où a sera true et b sera false à cause de batching .

Cependant, si b est dépendant de a, il se peut qu'il y ait une situation où vous ne pourriez pas obtenir 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 tous les appels ci-dessus traités, this.state.value sera égal à 1 et non 3 comme prévu.

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

5
Michal

comme dans doc

setState () met en file d'attente modifie l'état du composant et indique à React que ce composant et ses enfants doivent être restitués avec le état mis à jour. C'est la méthode principale que vous utilisez pour mettre à jour l'interface utilisateur en réponse aux gestionnaires d'événements et aux réponses du serveur.

le changement sera effectué comme dans la file d'attente (FIFO: premier entré premier sorti) le premier appel sera le premier à préformer

3
Ali

Plusieurs appels au cours du même cycle peuvent être mis en lot. Par exemple, si vous essayez d’incrémenter une quantité d’article plus d’une fois dans le même cycle, l’équivalent de:

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

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

2
Mosè Raguzzini