Pourquoi setState est-il dans reactjs Async au lieu de Sync?

127

Je viens de découvrir que la this.setState()fonction react dans n'importe quel composant est asynchrone ou est appelée après l'achèvement de la fonction dans laquelle elle a été appelée.

Maintenant, j'ai cherché et trouvé ce blog ( l'opération de mutation d'état setState () peut être synchrone dans ReactJS )

Ici, il a trouvé que setStatec'est async (appelé lorsque la pile est vide) ou sync (appelé dès qu'il est appelé) en fonction de la façon dont le changement d'état a été déclenché.

Maintenant, ces deux choses sont difficiles à digérer

  1. Dans le blog, la setStatefonction est appelée à l'intérieur d'une fonction updateState, mais ce qui a déclenché la updateStatefonction n'est pas quelque chose qu'une fonction appelée connaîtrait.
  2. Pourquoi feraient-ils une setStateasynchrone car JS est un langage à thread unique et que setState n'est pas un appel WebAPI ou serveur, il doit donc être effectué uniquement sur le thread de JS. Font-ils cela pour que le re-rendu n'arrête pas tous les écouteurs d'événements et autres, ou il y a un autre problème de conception.
Anup
la source
1
J'ai écrit un article aujourd'hui qui aide à décrire un peu le climat autour setState: medium.com/@agm1984/…
agm1984

Réponses:

157

Vous pouvez appeler une fonction après la mise à jour de la valeur d'état:

this.setState({foo: 'bar'}, () => { 
    // Do something here. 
});

De plus, si vous avez beaucoup d'états à mettre à jour à la fois, regroupez-les tous dans le même setState:

Au lieu de:

this.setState({foo: "one"}, () => {
    this.setState({bar: "two"});
});

Faites juste ceci:

this.setState({
    foo: "one",
    bar: "two"
});
JoeTidee
la source
18
ya thats ok nous avons une fonction callBack que nous pouvons utiliser mais dats pas la question.
Anup
12
J'espère que cela aidera quelqu'un d'autre à trébucher sur cette question.
JoeTidee
2
ya dat peut être utile
Anup
97

1) les setStateactions sont asynchrones et sont groupées pour des gains de performances. Ceci est expliqué dans la documentation de setState.

setState () ne modifie pas immédiatement this.state mais crée une transition d'état en attente. Accéder à this.state après avoir appelé cette méthode peut potentiellement renvoyer la valeur existante. Il n'y a aucune garantie de fonctionnement synchrone des appels à setState et les appels peuvent être groupés pour des gains de performances.


2) Pourquoi rendraient-ils setState asynchrone car JS est un langage à thread unique et qu'il setStatene s'agit pas d'un appel WebAPI ou serveur?

C'est parce que setStatemodifie l'état et provoque un nouveau rendu. Cela peut être une opération coûteuse et la rendre synchrone peut laisser le navigateur sans réponse.

Ainsi, les appels setState sont asynchrones et groupés pour une meilleure expérience et des performances d'interface utilisateur.

Sachin
la source
59
Si vous devez vous assurer de l'ordre des événements après un appel setState, vous pouvez passer une fonction de rappel. this.setState({ something: true }, () => console.log(this.state))
ianks
1
Merci @Sachin pour l'explication. Cependant, j'ai encore des doutes, peut-il être synchrone comme l'explique le blog?
Ajay Gaur
2
Une autre décision de conception stupide en réaction. Rendre la mise à jour de l'état synchrone et le rendu asynchrone. Vous pouvez effectuer des rendus par lots, mais je veux pouvoir définir quelque chose d'aussi primitif que des variables d'état sans avoir à gérer les conditions de concurrence.
ig-dev
Pourquoi ne pas autoriser la définition d'une option pour que la fonction soit asynchrone ou synchronisée? Ce serait une fonctionnalité utile
Randall Coding
Évidemment, je ne fais pas partie de l'équipe de réaction, mais une des raisons, à mon avis, de rendre la mise à jour de l'état asynchrone est que les navigateurs sont à thread unique. Les opérations de synchronisation peuvent empêcher l'interface utilisateur de répondre et ne sont pas un bon candidat pour l'interface utilisateur.
Sachin
16

Je sais que cette question est ancienne, mais elle a causé beaucoup de confusion pour de nombreux utilisateurs de reactjs depuis longtemps, y compris moi. Récemment, Dan Abramov (de l'équipe de réaction) vient d'écrire une excellente explication sur les raisons pour lesquelles la nature de setStateest asynchrone:

https://github.com/facebook/react/issues/11527#issuecomment-360199710

setStateest censé être asynchrone, et il y a quelques très bonnes raisons à cela dans l'explication liée de Dan Abramov. Cela ne veut pas dire qu'il sera toujours asynchrone - cela signifie principalement que vous ne pouvez tout simplement pas dépendre de sa synchronisation . ReactJS prend en compte de nombreuses variables dans le scénario dans lequel vous modifiez l'état, pour décider quand le statedoit être réellement mis à jour et votre composant rendu.
Un exemple simple pour illustrer cela est que si vous appelez setStateen réaction à une action de l'utilisateur, le statesera probablement mis à jour immédiatement (même si, encore une fois, vous ne pouvez pas compter dessus), de sorte que l'utilisateur ne ressentira aucun retard , mais si vous appelezsetState en réaction à une réponse d'appel ajax ou à un autre événement qui n'est pas déclenché par l'utilisateur, l'état peut être mis à jour avec un léger retard, car l'utilisateur ne ressentira pas vraiment ce retard et améliorera les performances en attendant groupez plusieurs mises à jour d'état ensemble et rendez le DOM moins de fois.

gillyb
la source
Vous n'avez marqué aucune réponse comme étant la bonne. Les gens affichent comment le contourner. Pas la réponse à la question posée. cet article semble bon.
Anup
@Anup La réponse est un peu plus compliquée que simplement «async» ou «sync». Il doit toujours être traité comme «asynchrone», mais dans certains cas, il peut agir «sync». J'espère que j'ai fait la lumière pour vous.
gillyb
8

Bon article ici https://github.com/vasanthk/react-bits/blob/master/patterns/27.passing-function-to-setState.md

// assuming this.state.count === 0
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
this.setState({count: this.state.count + 1});
// this.state.count === 1, not 3

Solution
this.setState((prevState, props) => ({
  count: prevState.count + props.increment
}));

ou passer le rappel this.setState ({.....},callback)

https://medium.com/javascript-scene/setstate-gate-abc10a9b2d82 https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

zloctb
la source
7

Vous pouvez utiliser l'habillage suivant pour effectuer un appel de synchronisation

this.setState((state =>{
  return{
    something
  }
})

Ярослав
la source
réponse sous-estimée
James Cat
1

Imaginez incrémenter un compteur dans un composant:

  class SomeComponent extends Component{

    state = {
      updatedByDiv: '',
      updatedByBtn: '',
      counter: 0
    }

    divCountHandler = () => {
      this.setState({
        updatedByDiv: 'Div',
        counter: this.state.counter + 1
      });
      console.log('divCountHandler executed');
    }

    btnCountHandler = () => {
      this.setState({
        updatedByBtn: 'Button',
        counter: this.state.counter + 1
      });
      console.log('btnCountHandler executed');
    }
    ...
    ...
    render(){
      return (
        ...
        // a parent div
        <div onClick={this.divCountHandler}>
          // a child button
          <button onClick={this.btnCountHandler}>Increment Count</button>
        </div>
        ...
      )
    }
  }

Il existe un gestionnaire de comptage attaché aux composants parent et enfant. Ceci est fait exprès afin que nous puissions exécuter le setState () deux fois dans le même contexte de bullage d'événement de clic, mais à partir de 2 gestionnaires différents.

Comme nous l'imaginons, un seul événement de clic sur le bouton déclencherait maintenant ces deux gestionnaires puisque l'événement bouillonnait de la cible vers le conteneur le plus à l'extérieur pendant la phase de bullage.

Par conséquent, le btnCountHandler () s'exécute en premier, devrait incrémenter le nombre à 1, puis le divCountHandler () s'exécute, devrait incrémenter le nombre à 2.

Cependant, le nombre augmente uniquement à 1 comme vous pouvez l'inspecter dans les outils React Developer.

Cela prouve que réagissent

  • met en file d'attente tous les appels setState

  • revient dans cette file d'attente après avoir exécuté la dernière méthode dans le contexte (le divCountHandler dans ce cas)

  • fusionne toutes les mutations d'objet qui se produisent dans plusieurs appels setState dans le même contexte (tous les appels de méthode dans une seule phase d'événement sont le même contexte pour, par exemple) en une seule syntaxe de mutation d'objet (la fusion a du sens car c'est pourquoi nous pouvons mettre à jour les propriétés de l'état indépendamment dans setState () en premier lieu)

  • et le transmet à un seul setState () pour empêcher le re-rendu dû à plusieurs appels à setState () (c'est une description très primitive du traitement par lots).

Code résultant exécuté par react:

this.setState({
  updatedByDiv: 'Div',
  updatedByBtn: 'Button',
  counter: this.state.counter + 1
})

Pour arrêter ce comportement, au lieu de passer des objets en tant qu'arguments à la méthode setState, des rappels sont passés.

    divCountHandler = () => {
          this.setState((prevState, props) => {
            return {
              updatedByDiv: 'Div',
              counter: prevState.counter + 1
            };
          });
          console.log('divCountHandler executed');
        }

    btnCountHandler = () => {
          this.setState((prevState, props) => {
            return {
              updatedByBtn: 'Button',
              counter: prevState.counter + 1
            };
          });
      console.log('btnCountHandler executed');
    }

Une fois que la dernière méthode a terminé son exécution et que react revient pour traiter la file d'attente setState, elle appelle simplement le rappel pour chaque setState mis en file d'attente, en passant l'état précédent du composant.

De cette façon, react garantit que le dernier rappel de la file d'attente met à jour l'état sur lequel tous ses homologues précédents ont mis la main.

supi
la source
0

Oui, setState () est asynchrone.

Depuis le lien: https://reactjs.org/docs/react-component.html#setstate

  • React ne garantit pas que les changements d'état sont appliqués immédiatement.
  • setState () ne met pas toujours immédiatement à jour le composant.
  • Considérez setState () comme une requête plutôt qu'une commande immédiate pour mettre à jour le composant.

Parce qu'ils pensent
du lien: https://github.com/facebook/react/issues/11527#issuecomment-360199710

... nous convenons que le re-rendu de setState () de manière synchrone serait inefficace dans de nombreux cas

La fonction asynchrone setState () rend la vie très difficile pour les débutants et même les expérimentés malheureusement:
- problèmes de rendu inattendus: rendu retardé ou pas de rendu (basé sur la logique du programme)
- passer des paramètres est un gros problème
parmi d'autres problèmes.

L'exemple ci-dessous a aidé:

// call doMyTask1 - here we set state
// then after state is updated...
//     call to doMyTask2 to proceed further in program

constructor(props) {
    // ..

    // This binding is necessary to make `this` work in the callback
    this.doMyTask1 = this.doMyTask1.bind(this);
    this.doMyTask2 = this.doMyTask2.bind(this);
}

function doMyTask1(myparam1) {
    // ..

    this.setState(
        {
            mystate1: 'myvalue1',
            mystate2: 'myvalue2'
            // ...
        },    
        () => {
            this.doMyTask2(myparam1); 
        }
    );
}

function doMyTask2(myparam2) {
    // ..
}

J'espère que cela pourra aider.

Manohar Reddy Poreddy
la source