React: Clarification RFC¬†: pourquoi ¬ę¬†setState¬†¬Ľ est-il asynchrone¬†?

Cr√©√© le 11 nov. 2017  ¬∑  31Commentaires  ¬∑  Source: facebook/react

Pendant un bon moment, j'ai essayé de comprendre pourquoi setState est asynchrone. Et à défaut de trouver une réponse dans le passé, je suis arrivé à la conclusion que c'était pour des raisons historiques et probablement difficile à changer maintenant. Cependant @gaearon a indiqué qu'il y a une raison claire, donc je suis curieux de savoir :)

Quoi qu'il en soit, voici les raisons que j'entends souvent, mais je pense qu'elles ne peuvent pas être tout car elles sont trop faciles à contrer

Async setState est requis pour le rendu asynchrone

Beaucoup pensent initialement que c'est à cause de l'efficacité du rendu. Mais je ne pense pas que ce soit la raison de ce comportement, car garder la synchronisation de setState avec le rendu async semble trivial pour moi, quelque chose comme :

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

En fait, par exemple mobx-react permet des affectations synchrones à des observables tout en respectant la nature asynchrone du rendu

Async setState est nécessaire pour savoir quel état a été _rendu_

L'autre argument que j'entends parfois est que vous voulez raisonner sur l'√©tat qui a √©t√© rendu, pas l'√©tat qui a √©t√© demand√©. Mais je ne suis pas s√Ľr que ce principe ait beaucoup de m√©rite non plus. Conceptuellement, cela me semble √©trange. Le rendu est un effet secondaire, l'√©tat concerne les faits. Aujourd'hui, j'ai 32 ans, et l'ann√©e prochaine j'aurai 33 ans, que le composant propri√©taire r√©ussisse √† restituer cette ann√©e ou non :).

Pour faire un parallèle (probablement pas très bon) : si vous ne pouviez pas _lire_ votre dernière version d'un document Word écrit par vous-même jusqu'à ce que vous l'imprimiez, ce serait assez gênant. Je ne pense pas, par exemple, que les moteurs de jeu vous donnent des informations sur l'état du jeu qui a été rendu exactement et sur les images qui ont été supprimées.

Une observation intéressante : En 2 ans mobx-react personne ne m'a jamais posé la question : Comment puis-je savoir que mes observables sont rendus ? Cette question ne semble tout simplement pas pertinente très souvent.

J'ai rencontr√© quelques cas o√Ļ il √©tait pertinent de savoir quelles donn√©es √©taient rendues. Le cas dont je me souviens √©tait celui o√Ļ j'avais besoin de conna√ģtre les dimensions en pixels de certaines donn√©es √† des fins de mise en page. Mais cela a √©t√© √©l√©gamment r√©solu en utilisant didComponentUpdate et ne d√©pendait pas non plus du fait que setState √©tait asynchrone. Ces cas semblent si rares qu'il ne justifie gu√®re de concevoir l'API principalement autour d'eux. Si cela peut √™tre fait d'une mani√®re ou d'une autre , cela suffit je pense


Je n'ai aucun doute que l'équipe React est consciente de la confusion que la nature asynchrone de setState introduit souvent, donc je soupçonne qu'il y a une autre très bonne raison pour la sémantique actuelle. Dis m'en plus :)

Discussion

Commentaire le plus utile

Voici donc quelques réflexions. Ce n'est en aucun cas une réponse complète, mais c'est peut-être encore plus utile que de ne rien dire.

Tout d'abord, je pense que nous convenons que retarder la réconciliation afin de regrouper les mises à jour est bénéfique. C'est-à-dire que nous convenons que le re-rendu de setState() synchrone serait inefficace dans de nombreux cas, et il est préférable de mettre à jour par lots si nous savons que nous en aurons probablement plusieurs.

Par exemple, si nous sommes dans un gestionnaire de navigateur click , et que Child et Parent appellent setState , nous ne voulons pas refaire le rendu les Child deux fois, et préfèrent à la place les marquer comme sales et les restituer ensemble avant de quitter l'événement du navigateur.

Vous demandez : pourquoi ne pouvons-nous pas faire exactement la même chose (batching) mais écrire les mises setState jour this.state sans attendre la fin de la réconciliation. Je ne pense pas qu'il y ait une réponse évidente (l'une ou l'autre solution a des compromis) mais voici quelques raisons auxquelles je peux penser.

Garantir la cohérence interne

M√™me si state est mis √† jour de mani√®re synchrone, props ne le sont pas. (Vous ne pouvez pas conna√ģtre props tant que vous n'avez pas rendu √† nouveau le composant parent, et si vous le faites de mani√®re synchrone, le traitement par lots sort de la fen√™tre.)

À l'heure actuelle, les objets fournis par React ( state , props , refs ) sont cohérents entre eux. Cela signifie que si vous n'utilisez que ces objets, ils sont garantis de faire référence à un arbre entièrement réconcilié (même s'il s'agit d'une version plus ancienne de cet arbre). Pourquoi est-ce important ?

Lorsque vous utilisez uniquement l'état, s'il est vidé de manière synchrone (comme vous l'avez proposé), ce modèle fonctionnerait :

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

Cependant, disons que cet état doit être levé pour être partagé entre quelques composants afin de le déplacer vers un parent :

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Je tiens à souligner que dans les applications React typiques qui reposent sur setState() il s'agit du type de refactorisation spécifique à React le plus courant que vous feriez quotidiennement .

Cependant, cela casse notre code !

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

En effet, dans le modèle que vous avez proposé, this.state serait immédiatement vidé, mais pas this.props . Et nous ne pouvons pas immédiatement vider this.props sans re-rendre le parent, ce qui signifie que nous devrions renoncer au traitement par lots (qui, selon les cas, peut dégrader les performances de manière très significative).

Il existe également des cas plus subtils de la façon dont cela peut casser, par exemple si vous mélangez des données de props (pas encore vidés) et de state (proposé pour être vidés immédiatement) pour créer un nouvel état : https://github.com/facebook/react/issues/122#issuecomment -81856416. Les références présentent le même problème : https://github.com/facebook/react/issues/122#issuecomment -22659651.

Ces exemples ne sont pas du tout théoriques. En fait, les liaisons React Redux avaient exactement ce genre de problème car elles mélangeaient des accessoires React avec un état non-React : https://github.com/reactjs/react-redux/issues/86 , https://github.com/ reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/issues/525.

Je ne sais pas pourquoi les utilisateurs de MobX ne se sont pas heurt√©s √† cela, mais mon intuition est qu'ils pourraient se heurter √† de tels sc√©narios mais les consid√®rent comme leur propre faute. Ou peut-√™tre qu'ils ne lisent pas autant √† partir de props et qu'ils lisent plut√īt directement √† partir d'objets mutables MobX.

Alors, comment React r√©sout-il ce probl√®me aujourd'hui¬†? Dans React, this.state et this.props ne sont mis √† jour qu'apr√®s la r√©conciliation et le vidage, vous verriez donc 0 imprim√©s avant et apr√®s la refactorisation. Cela rend l'√©tat de levage s√Ľr.

Oui, cela peut être gênant dans certains cas. Surtout pour les personnes venant d'horizons plus OO qui veulent juste muter l'état plusieurs fois au lieu de penser comment représenter une mise à état en un seul endroit. Je peux comprendre cela, même si je pense que garder les mises à jour d'état concentrées est plus clair du point de vue du débogage : https://github.com/facebook/react/issues/122#issuecomment -19888472.

Néanmoins, vous avez la possibilité de déplacer l'état que vous souhaitez lire immédiatement dans un objet mutable latéralement, en particulier si vous ne l'utilisez pas comme source de vérité pour le rendu. C'est à peu près ce que MobX vous permet de faire .

Vous avez également la possibilité de vider l'ensemble de l'arbre si vous savez ce que vous faites. L'API s'appelle ReactDOM.flushSync(fn) . Je ne pense pas que nous l'ayons encore documenté, mais nous le ferons certainement à un moment donné au cours du cycle de publication de la 16.x. Notez qu'il force en fait un nouveau rendu complet pour les mises à jour qui se produisent à l'intérieur de l'appel, vous devez donc l'utiliser avec parcimonie. De cette façon, cela ne brise pas la garantie de cohérence interne entre props , state , et refs .

Pour r√©sumer, le mod√®le React ne conduit pas toujours au code le plus concis, mais il est coh√©rent en interne et garantit que l'√©tat de levage est s√Ľr .

Activation des mises à jour simultanées

Conceptuellement, React se comporte comme s'il avait une seule file d'attente de mise à jour par composant. C'est pourquoi la discussion a du sens : nous discutons de l'opportunité d'appliquer les mises à jour à this.state immédiatement ou non, car nous n'avons aucun doute que les mises à jour seront appliquées dans cet ordre exact. Cependant, cela n'a pas besoin d'être le cas ( haha ).

R√©cemment, nous avons beaucoup parl√© de ¬ę rendu asynchrone ¬Ľ. J'admets que nous n'avons pas fait un tr√®s bon travail pour communiquer ce que cela signifie, mais c'est la nature de la R&D : vous allez apr√®s une id√©e qui semble conceptuellement prometteuse, mais vous ne comprenez vraiment ses implications qu'apr√®s avoir pass√© suffisamment de temps avec elle.

Une fa√ßon dont nous avons expliqu√© le ¬ę rendu asynchrone ¬Ľ est que React pourrait attribuer diff√©rentes priorit√©s aux appels setState() fonction de leur provenance¬†: un gestionnaire d'√©v√©nements, une r√©ponse r√©seau, une animation, etc.

Par exemple, si vous saisissez un message, les appels setState() dans le composant TextBox doivent être immédiatement vidés. Cependant, si vous recevez un nouveau message pendant que vous tapez , il est probablement préférable de retarder le rendu du nouveau MessageBubble jusqu'à un certain seuil (par exemple une seconde) que de laisser la frappe bégayer en bloquant le fil.

Si nous laissons certaines mises √† jour avoir une ¬ę priorit√© inf√©rieure ¬Ľ, nous pourrions diviser leur rendu en petits morceaux de quelques millisecondes afin qu'ils ne soient pas perceptibles par l'utilisateur.

Je sais que des optimisations de performances comme celle-ci peuvent ne pas sembler tr√®s excitantes ou convaincantes. Vous pourriez dire¬†: ¬ę¬†nous n'avons pas besoin de cela avec MobX, notre suivi des mises √† jour est suffisamment rapide pour √©viter simplement les re-rendus¬†¬Ľ. Je ne pense pas que ce soit vrai dans tous les cas (par exemple, quelle que soit la vitesse de MobX, vous devez toujours cr√©er des nŇďuds DOM et effectuer le rendu pour les vues nouvellement mont√©es). Pourtant, si c'√©tait vrai, et si vous d√©cidiez consciemment que vous √™tes d'accord pour toujours encapsuler les objets dans une biblioth√®que JavaScript sp√©cifique qui suit les lectures et les √©critures, vous ne b√©n√©ficierez peut-√™tre pas autant de ces optimisations.

Mais le rendu asynchrone ne se limite pas à l'optimisation des performances.

Par exemple, consid√©rons le cas o√Ļ vous naviguez d'un √©cran √† un autre. En r√®gle g√©n√©rale, vous affichez une fl√®che pendant le rendu du nouvel √©cran.

Cependant, si la navigation est suffisamment rapide (en l'espace d'une seconde environ), le fait de clignoter et de masquer imm√©diatement un spinner entra√ģne une d√©gradation de l'exp√©rience utilisateur. Pire encore, si vous avez plusieurs niveaux de composants avec diff√©rentes d√©pendances asynchrones (donn√©es, code, images), vous vous retrouvez avec une cascade de spinners qui clignotent bri√®vement un par un. Ceci est √† la fois visuellement d√©sagr√©able et rend votre application plus lente dans la pratique en raison de tous les reflows du DOM. C'est aussi la source de beaucoup de code passe-partout.

Ne serait-il pas bien si, lorsque vous faites un simple setState() qui rend une vue diff√©rente, nous pouvions ¬ę¬†commencer¬†¬Ľ √† rendre la vue mise √† jour ¬ę¬†en arri√®re-plan¬†¬Ľ¬†? Imaginez que sans √©crire vous-m√™me de code de coordination, vous puissiez choisir d'afficher un spinner si la mise √† jour a pris plus d'un certain seuil (par exemple une seconde), et sinon laisser React effectuer une transition transparente lorsque les d√©pendances asynchrones du nouveau sous-arbre sont satisfait . De plus, pendant que nous ¬ęattendons¬Ľ, l'¬ęancien √©cran¬Ľ reste interactif (par exemple, vous pouvez choisir un √©l√©ment diff√©rent vers lequel effectuer la transition), et React impose que si cela prend trop de temps, vous devez afficher un spinner.

Il s'av√®re qu'avec le mod√®le React actuel et quelques ajustements des cycles de vie , nous pouvons r√©ellement impl√©menter cela ! @acdlite travaille sur cette fonctionnalit√© depuis quelques semaines et publiera bient√īt une RFC pour cela.

Notez que cela n'est possible que parce que this.state n'est pas vid√© imm√©diatement. S'il √©tait vid√© imm√©diatement, nous n'aurions aucun moyen de commencer √† afficher une ¬ę nouvelle version ¬Ľ de la vue en arri√®re-plan alors que ¬ę l'ancienne version ¬Ľ est toujours visible et interactive. Leurs mises √† jour ind√©pendantes de l'√Čtat se heurteraient.

Je ne veux pas voler le tonnerre √† @acdlite en ce qui concerne l'annonce de tout cela, mais j'esp√®re que cela semble au moins un peu excitant. Je comprends que cela puisse encore ressembler √† du vaporware, ou comme si nous ne savions pas vraiment ce que nous faisons. J'esp√®re que nous pourrons vous convaincre du contraire dans les prochains mois, et que vous appr√©cierez la flexibilit√© du mod√®le React. Et pour autant que je sache, au moins en partie, cette flexibilit√© est possible gr√Ęce au non- vidage imm√©diat des mises √† jour d'√©tat.

Tous les 31 commentaires

On attend tous @gaearon .

@Kaybarax Hé, c'est le week-end ;-)

@mweststrate Oh ! ma faute. Cool.

Je vais prendre des risques ici et dire que c'est à cause du regroupement de plusieurs setState s dans la même tique.

Je pars en vacances la semaine prochaine mais j'irai probablement mardi donc j'essaierai de répondre lundi.

fonction enqueueUpdate (composant) {
assurerInjecté();

// Diverses parties de notre code (telles que ReactCompositeComponent's
// _renderValidatedComponent) supposent que les appels au rendu ne sont pas imbriqués ;
// vérifie que c'est le cas. (Ceci est appelé par chaque mise à jour de niveau supérieur
// fonction, comme setState, forceUpdate, etc. ; création et
// la destruction des composants de niveau supérieur est protégée dans ReactMount.)

if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, composant);
retourner;
}

dirtyComponents.push(composant);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1 ;
}
}

@mweststrate juste 2 cents : c'est une question très valable.
Je suis s√Ľr que nous sommes tous d'accord qu'il serait beaucoup plus facile de raisonner sur l'√©tat si setState √©tait synchrone.
Quelles que soient les raisons de rendre setState asynchrone, je ne suis pas s√Ľr que l'√©quipe r√©agisse bien par rapport aux inconv√©nients que cela introduirait, par exemple la difficult√© de raisonner sur l'√©tat actuel et la confusion que cela apporte aux d√©veloppeurs.

@mweststrate fait intéressant, j'ai posé la même question ici : https://discuss.reactjs.org/t/historic-reasons-behind-setstate-not-being-immediately-visible/8487

J'ai personnellement eu et vu chez d'autres développeurs une confusion à ce sujet. @gaearon ce serait bien d'avoir une explication à ce sujet quand vous aurez un peu de temps :)

Désolé, c'est la fin de l'année et nous avons pris un peu de retard sur GitHub, etc., essayant de conclure tout ce sur quoi nous avons travaillé avant les vacances.

J'ai l'intention de revenir sur ce fil et d'en discuter. Mais c'est aussi une cible un peu mouvante car nous travaillons actuellement sur des fonctionnalités asynchrones de React qui sont directement liées à comment et quand this.state est mis à jour. Je ne veux pas passer beaucoup de temps à écrire quelque chose, puis devoir le réécrire parce que les hypothèses sous-jacentes ont changé. J'aimerais donc garder cela ouvert, mais je ne sais pas encore quand je pourrai donner une réponse définitive.

Voici donc quelques réflexions. Ce n'est en aucun cas une réponse complète, mais c'est peut-être encore plus utile que de ne rien dire.

Tout d'abord, je pense que nous convenons que retarder la réconciliation afin de regrouper les mises à jour est bénéfique. C'est-à-dire que nous convenons que le re-rendu de setState() synchrone serait inefficace dans de nombreux cas, et il est préférable de mettre à jour par lots si nous savons que nous en aurons probablement plusieurs.

Par exemple, si nous sommes dans un gestionnaire de navigateur click , et que Child et Parent appellent setState , nous ne voulons pas refaire le rendu les Child deux fois, et préfèrent à la place les marquer comme sales et les restituer ensemble avant de quitter l'événement du navigateur.

Vous demandez : pourquoi ne pouvons-nous pas faire exactement la même chose (batching) mais écrire les mises setState jour this.state sans attendre la fin de la réconciliation. Je ne pense pas qu'il y ait une réponse évidente (l'une ou l'autre solution a des compromis) mais voici quelques raisons auxquelles je peux penser.

Garantir la cohérence interne

M√™me si state est mis √† jour de mani√®re synchrone, props ne le sont pas. (Vous ne pouvez pas conna√ģtre props tant que vous n'avez pas rendu √† nouveau le composant parent, et si vous le faites de mani√®re synchrone, le traitement par lots sort de la fen√™tre.)

À l'heure actuelle, les objets fournis par React ( state , props , refs ) sont cohérents entre eux. Cela signifie que si vous n'utilisez que ces objets, ils sont garantis de faire référence à un arbre entièrement réconcilié (même s'il s'agit d'une version plus ancienne de cet arbre). Pourquoi est-ce important ?

Lorsque vous utilisez uniquement l'état, s'il est vidé de manière synchrone (comme vous l'avez proposé), ce modèle fonctionnerait :

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

Cependant, disons que cet état doit être levé pour être partagé entre quelques composants afin de le déplacer vers un parent :

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Je tiens à souligner que dans les applications React typiques qui reposent sur setState() il s'agit du type de refactorisation spécifique à React le plus courant que vous feriez quotidiennement .

Cependant, cela casse notre code !

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

En effet, dans le modèle que vous avez proposé, this.state serait immédiatement vidé, mais pas this.props . Et nous ne pouvons pas immédiatement vider this.props sans re-rendre le parent, ce qui signifie que nous devrions renoncer au traitement par lots (qui, selon les cas, peut dégrader les performances de manière très significative).

Il existe également des cas plus subtils de la façon dont cela peut casser, par exemple si vous mélangez des données de props (pas encore vidés) et de state (proposé pour être vidés immédiatement) pour créer un nouvel état : https://github.com/facebook/react/issues/122#issuecomment -81856416. Les références présentent le même problème : https://github.com/facebook/react/issues/122#issuecomment -22659651.

Ces exemples ne sont pas du tout théoriques. En fait, les liaisons React Redux avaient exactement ce genre de problème car elles mélangeaient des accessoires React avec un état non-React : https://github.com/reactjs/react-redux/issues/86 , https://github.com/ reactjs/react-redux/pull/99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https://github. com/reactjs/react-redux/issues/525.

Je ne sais pas pourquoi les utilisateurs de MobX ne se sont pas heurt√©s √† cela, mais mon intuition est qu'ils pourraient se heurter √† de tels sc√©narios mais les consid√®rent comme leur propre faute. Ou peut-√™tre qu'ils ne lisent pas autant √† partir de props et qu'ils lisent plut√īt directement √† partir d'objets mutables MobX.

Alors, comment React r√©sout-il ce probl√®me aujourd'hui¬†? Dans React, this.state et this.props ne sont mis √† jour qu'apr√®s la r√©conciliation et le vidage, vous verriez donc 0 imprim√©s avant et apr√®s la refactorisation. Cela rend l'√©tat de levage s√Ľr.

Oui, cela peut être gênant dans certains cas. Surtout pour les personnes venant d'horizons plus OO qui veulent juste muter l'état plusieurs fois au lieu de penser comment représenter une mise à état en un seul endroit. Je peux comprendre cela, même si je pense que garder les mises à jour d'état concentrées est plus clair du point de vue du débogage : https://github.com/facebook/react/issues/122#issuecomment -19888472.

Néanmoins, vous avez la possibilité de déplacer l'état que vous souhaitez lire immédiatement dans un objet mutable latéralement, en particulier si vous ne l'utilisez pas comme source de vérité pour le rendu. C'est à peu près ce que MobX vous permet de faire .

Vous avez également la possibilité de vider l'ensemble de l'arbre si vous savez ce que vous faites. L'API s'appelle ReactDOM.flushSync(fn) . Je ne pense pas que nous l'ayons encore documenté, mais nous le ferons certainement à un moment donné au cours du cycle de publication de la 16.x. Notez qu'il force en fait un nouveau rendu complet pour les mises à jour qui se produisent à l'intérieur de l'appel, vous devez donc l'utiliser avec parcimonie. De cette façon, cela ne brise pas la garantie de cohérence interne entre props , state , et refs .

Pour r√©sumer, le mod√®le React ne conduit pas toujours au code le plus concis, mais il est coh√©rent en interne et garantit que l'√©tat de levage est s√Ľr .

Activation des mises à jour simultanées

Conceptuellement, React se comporte comme s'il avait une seule file d'attente de mise à jour par composant. C'est pourquoi la discussion a du sens : nous discutons de l'opportunité d'appliquer les mises à jour à this.state immédiatement ou non, car nous n'avons aucun doute que les mises à jour seront appliquées dans cet ordre exact. Cependant, cela n'a pas besoin d'être le cas ( haha ).

R√©cemment, nous avons beaucoup parl√© de ¬ę rendu asynchrone ¬Ľ. J'admets que nous n'avons pas fait un tr√®s bon travail pour communiquer ce que cela signifie, mais c'est la nature de la R&D : vous allez apr√®s une id√©e qui semble conceptuellement prometteuse, mais vous ne comprenez vraiment ses implications qu'apr√®s avoir pass√© suffisamment de temps avec elle.

Une fa√ßon dont nous avons expliqu√© le ¬ę rendu asynchrone ¬Ľ est que React pourrait attribuer diff√©rentes priorit√©s aux appels setState() fonction de leur provenance¬†: un gestionnaire d'√©v√©nements, une r√©ponse r√©seau, une animation, etc.

Par exemple, si vous saisissez un message, les appels setState() dans le composant TextBox doivent être immédiatement vidés. Cependant, si vous recevez un nouveau message pendant que vous tapez , il est probablement préférable de retarder le rendu du nouveau MessageBubble jusqu'à un certain seuil (par exemple une seconde) que de laisser la frappe bégayer en bloquant le fil.

Si nous laissons certaines mises √† jour avoir une ¬ę priorit√© inf√©rieure ¬Ľ, nous pourrions diviser leur rendu en petits morceaux de quelques millisecondes afin qu'ils ne soient pas perceptibles par l'utilisateur.

Je sais que des optimisations de performances comme celle-ci peuvent ne pas sembler tr√®s excitantes ou convaincantes. Vous pourriez dire¬†: ¬ę¬†nous n'avons pas besoin de cela avec MobX, notre suivi des mises √† jour est suffisamment rapide pour √©viter simplement les re-rendus¬†¬Ľ. Je ne pense pas que ce soit vrai dans tous les cas (par exemple, quelle que soit la vitesse de MobX, vous devez toujours cr√©er des nŇďuds DOM et effectuer le rendu pour les vues nouvellement mont√©es). Pourtant, si c'√©tait vrai, et si vous d√©cidiez consciemment que vous √™tes d'accord pour toujours encapsuler les objets dans une biblioth√®que JavaScript sp√©cifique qui suit les lectures et les √©critures, vous ne b√©n√©ficierez peut-√™tre pas autant de ces optimisations.

Mais le rendu asynchrone ne se limite pas à l'optimisation des performances.

Par exemple, consid√©rons le cas o√Ļ vous naviguez d'un √©cran √† un autre. En r√®gle g√©n√©rale, vous affichez une fl√®che pendant le rendu du nouvel √©cran.

Cependant, si la navigation est suffisamment rapide (en l'espace d'une seconde environ), le fait de clignoter et de masquer imm√©diatement un spinner entra√ģne une d√©gradation de l'exp√©rience utilisateur. Pire encore, si vous avez plusieurs niveaux de composants avec diff√©rentes d√©pendances asynchrones (donn√©es, code, images), vous vous retrouvez avec une cascade de spinners qui clignotent bri√®vement un par un. Ceci est √† la fois visuellement d√©sagr√©able et rend votre application plus lente dans la pratique en raison de tous les reflows du DOM. C'est aussi la source de beaucoup de code passe-partout.

Ne serait-il pas bien si, lorsque vous faites un simple setState() qui rend une vue diff√©rente, nous pouvions ¬ę¬†commencer¬†¬Ľ √† rendre la vue mise √† jour ¬ę¬†en arri√®re-plan¬†¬Ľ¬†? Imaginez que sans √©crire vous-m√™me de code de coordination, vous puissiez choisir d'afficher un spinner si la mise √† jour a pris plus d'un certain seuil (par exemple une seconde), et sinon laisser React effectuer une transition transparente lorsque les d√©pendances asynchrones du nouveau sous-arbre sont satisfait . De plus, pendant que nous ¬ęattendons¬Ľ, l'¬ęancien √©cran¬Ľ reste interactif (par exemple, vous pouvez choisir un √©l√©ment diff√©rent vers lequel effectuer la transition), et React impose que si cela prend trop de temps, vous devez afficher un spinner.

Il s'av√®re qu'avec le mod√®le React actuel et quelques ajustements des cycles de vie , nous pouvons r√©ellement impl√©menter cela ! @acdlite travaille sur cette fonctionnalit√© depuis quelques semaines et publiera bient√īt une RFC pour cela.

Notez que cela n'est possible que parce que this.state n'est pas vid√© imm√©diatement. S'il √©tait vid√© imm√©diatement, nous n'aurions aucun moyen de commencer √† afficher une ¬ę nouvelle version ¬Ľ de la vue en arri√®re-plan alors que ¬ę l'ancienne version ¬Ľ est toujours visible et interactive. Leurs mises √† jour ind√©pendantes de l'√Čtat se heurteraient.

Je ne veux pas voler le tonnerre √† @acdlite en ce qui concerne l'annonce de tout cela, mais j'esp√®re que cela semble au moins un peu excitant. Je comprends que cela puisse encore ressembler √† du vaporware, ou comme si nous ne savions pas vraiment ce que nous faisons. J'esp√®re que nous pourrons vous convaincre du contraire dans les prochains mois, et que vous appr√©cierez la flexibilit√© du mod√®le React. Et pour autant que je sache, au moins en partie, cette flexibilit√© est possible gr√Ęce au non- vidage imm√©diat des mises √† jour d'√©tat.

Merveilleuse explication en profondeur des décisions derrière l'architecture de React. Merci.

marque

Merci, Dan.

Je ‚̧ԳŹ ce probl√®me. Super question et super r√©ponse. J'ai toujours pens√© que c'√©tait une mauvaise d√©cision de conception, maintenant je dois repenser

Merci, Dan.

Je l'appelle asyncAwesome setState :smile:

J'ai tendance à penser que tout doit être implémenté de manière asynchrone en premier, et si vous avez besoin d'une opération de synchronisation, eh bien, enveloppez l'opération asynchrone en attendant la fin. Il est beaucoup plus facile de faire du code de synchronisation à partir de code asynchrone (tout ce dont vous avez besoin est un wrapper) que l'inverse (ce qui nécessite essentiellement une réécriture complète, à moins que vous n'utilisiez le threading, ce qui n'est pas du tout léger).

@gaearon merci pour l'explication détaillée ! Cela me taraude depuis longtemps ("il doit y avoir une bonne raison, mais personne ne sait laquelle"). Mais maintenant, cela prend tout son sens et je vois à quel point c'est une décision vraiment consciente :). Merci beaucoup pour la réponse détaillée, je l'apprécie vraiment!

Ou peut-√™tre qu'ils ne lisent pas autant √† partir d'accessoires et qu'ils lisent plut√īt directement √† partir d'objets mutables MobX.

Je pense que c'est tout à fait vrai, dans MobX, les accessoires sont généralement utilisés comme une simple configuration de composant, et les données de domaine ne sont généralement pas capturées dans les accessoires, mais dans les entités de domaine qui sont transmises entre les composants.

Encore une fois, merci beaucoup !

@gaearon Merci pour l'explication détaillée et excellente.
Bien qu'il manque encore quelque chose ici, je pense que je comprends mais je veux √™tre s√Ľr.

Lorsque l'événement est enregistré "Outside React", cela signifie peut-être via addEventListener sur une ref par exemple. Ensuite, il n'y a pas de lot en cours.
Considérez ce code :

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

Lorsque nous cliquerons sur le bouton ¬ę¬†React Event¬†¬Ľ, nous verrons dans la console¬†:
"before setState" 0
"after setState" 0
Lorsque l'autre bouton "Direct DOM event" sera cliqué, nous verrons dans la console :
"before setState" 0
"after setState" 1

Après quelques petites recherches et navigation dans le code source, je pense savoir pourquoi cela se produit.
r√©agir ne peut pas contr√īler enti√®rement le flux de l'√©v√©nement et ne peut pas √™tre s√Ľr du moment et de la mani√®re dont le prochain √©v√©nement se d√©clenchera, donc en tant que "mode panique", il d√©clenchera simplement le changement d'√©tat imm√©diatement.

Quelles sont vos pensées à ce sujet? :en pensant:

@sag1v bien qu'un peu lié, il est probablement plus clair d'ouvrir un nouveau numéro pour de nouvelles questions. Utilisez simplement #11527 quelque part dans la description pour le lier à celui-ci.

@sag1v @gaearon m'avait donné une réponse très laconique ici https://twitter.com/dan_abramov/status/949992957180104704 . Je pense que son point de vue là-dessus me répondrait aussi plus concrètement.

@mweststrate J'ai pensé à ouvrir un nouveau numéro, mais j'ai réalisé que cela était directement lié à votre question "pourquoi setState asynchrone ?".
Comme cette discussion porte sur les décisions prises pour rendre setState "async", eh bien, j'ai pensé à ajouter quand et pourquoi le faire "synchroniser".
Cela ne me dérange pas d'ouvrir un nouveau problème si je ne vous ai pas convaincu que mon message est lié à ce problème :wink:

Ce @Kaybarax becuase votre question ¬ę est - il sync_ When ¬Ľ et non ¬ę _ Pourquoi est - il sync_ ¬Ľ ?.
Comme je l'ai mentionn√© dans mon message, je pense que je sais pourquoi, mais je veux √™tre s√Ľr et obtenir la r√©ponse officielle hehe. :le sourire:

r√©agir ne peut pas contr√īler compl√®tement le flux de l'√©v√©nement et ne peut pas √™tre s√Ľr de quand et comment le prochain √©v√©nement se d√©clenchera, donc en tant que "mode panique", il d√©clenchera simplement le changement d'√©tat imm√©diatement

Sorte de. Bien que cela ne soit pas exactement lié à la question de la mise à jour de this.state .

Ce que vous demandez, c'est dans quels cas React active le traitement par lots. React regroupe actuellement les mises à jour dans les gestionnaires d'événements gérés par React, car React "se trouve" dans le cadre supérieur de la pile d'appels et sait quand tous les gestionnaires d'événements React ont été exécutés. À ce stade, il vide la mise à jour.

Si le gestionnaire d'√©v√©nements n'est pas configur√© par React, il rend actuellement la mise √† jour synchrone. Parce qu'il ne sait pas s'il est prudent d'attendre ou non, et si d'autres mises √† jour auront lieu bient√īt.

Dans les futures versions de React, ce comportement changera. Le plan est de traiter les mises à jour comme étant de faible priorité par défaut afin qu'elles finissent par fusionner et regroupées (par exemple en une seconde), avec une option pour les vider immédiatement. Vous pouvez en savoir plus ici : https://github.com/facebook/react/issues/11171#issuecomment -357945371.

Impressionnant!

Cette question et cette réponse devraient être documentées dans un endroit plus accessible. Merci les gars de nous éclairer.

Appris beaucoup . Merci

J'essaie d'ajouter mon point de vue au fil. Je travaille sur une application basée sur MobX depuis quelques mois, j'explore ClojureScript depuis des années et j'ai créé ma propre alternative React (appelée Respo), j'ai essayé Redux au début bien que très peu de temps, et je parie sur ReasonML.

L'idée de base de la combinaison de React et de la programmation fonctionnelle (FP) est que vous disposez d'une donnée que vous pouvez restituer dans une vue avec toutes les compétences dont vous disposez et qui obéissent aux lois de la FP. Vous n'avez aucun effet secondaire si vous n'utilisez que des fonctions pures.

React n'est pas purement fonctionnel. En intégrant les états locaux à l'intérieur des composants, React a le pouvoir d'interagir avec diverses bibliothèques liées au DOM et à d'autres API de navigateur (également compatibles avec MobX), ce qui rend React impur. Cependant, j'ai essayé dans ClojureScript, si React est pur, cela pourrait être un désastre car il est vraiment difficile d'interagir avec autant de bibliothèques existantes qui ont des effets secondaires.

Donc, dans Respo (ma propre solution), j'avais deux objectifs qui semblent en conflit, 1) view = f(store) donc aucun état local n'est attendu ; 2) Je n'aime pas programmer tous les états de l'interface utilisateur des composants dans les réducteurs globaux car cela pourrait être difficile à maintenir. À la fin, j'ai compris que j'avais besoin d'un sucre de syntaxe, qui m'aide à maintenir les états des composants dans un magasin global avec paths , pendant ce temps j'écris des mises à jour d'état à l'intérieur du composant avec les macros Clojure.

Donc ce que j'ai appris : les états locaux sont une fonctionnalité d'expérience du développeur, en dessous nous voulons des états globaux qui permettent à nos moteurs d'effectuer des optimisations à des niveaux profonds. Donc, les gens de MobX préfèrent la POO, est-ce pour l'expérience des développeurs ou pour les moteurs ?

Au fait, j'ai parl√© de l'avenir de React et de ses fonctionnalit√©s asynchrones, au cas o√Ļ vous l'auriez manqu√©¬†:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Dan, tu es mon idole... merci beaucoup.

Cette page vous a été utile?
0 / 5 - 0 notes