React: RFClarification: ¬Ņpor qu√© `setState` es as√≠ncrono?

Creado en 11 nov. 2017  ¬∑  31Comentarios  ¬∑  Fuente: facebook/react

Durante bastante tiempo he intentado entender por qué setState es asincrónico. Y al no encontrar una respuesta en el pasado, llegué a la conclusión de que era por razones históricas y probablemente difícil de cambiar ahora. Sin embargo, @gaearon indicó que hay una razón clara, así que tengo curiosidad por averiguarlo :)

De todos modos, estas son las razones que escucho a menudo, pero creo que no pueden ser todo, ya que son demasiado f√°ciles de contrarrestar.

Se requiere setState asíncrono para la representación asíncrona

Muchos piensan inicialmente que se debe a la eficiencia del renderizado. Pero no creo que esa sea la razón detrás de este comportamiento, porque mantener la sincronización de setState con la representación asíncrona me suena trivial, algo como:

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

De hecho, por ejemplo, mobx-react permite asignaciones sincr√≥nicas a observables y a√ļn respeta la naturaleza asincr√≥nica de la representaci√≥n.

Se necesita async setState para saber qué estado se _rendered_

El otro argumento que escucho a veces es que usted quiere razonar sobre el estado que fue _remitido_, no el estado que fue _solicitado_. Pero tampoco estoy seguro de que este principio tenga mucho m√©rito. Conceptualmente me parece extra√Īo. La renderizaci√≥n es un efecto secundario, el estado se trata de hechos. Hoy, tengo 32 a√Īos y el pr√≥ximo a√Īo cumplir√© 33, independientemente de si el componente propietario se las arregla para volver a renderizar este a√Īo o no :).

Para dibujar un paralelo (probablemente no demasiado bueno): si no pudiera _leer_ su √ļltima versi√≥n de un documento de Word escrito por usted mismo hasta que lo imprimiera, ser√≠a bastante inc√≥modo. No creo que, por ejemplo, los motores de juegos te den retroalimentaci√≥n sobre qu√© estado del juego se renderiz√≥ exactamente y qu√© fotogramas se eliminaron.

Una observaci√≥n interesante: En 2 a√Īos mobx-react nadie me hizo la pregunta: ¬ŅC√≥mo s√© que se renderizan mis observables? Esta pregunta no parece relevante muy a menudo.

Encontr√© algunos casos en los que saber qu√© datos se procesaron era relevante. El caso que recuerdo fue en el que necesitaba conocer las dimensiones en p√≠xeles de algunos datos para fines de dise√Īo. Pero eso se resolvi√≥ elegantemente usando didComponentUpdate y tampoco confiaba en que setState fuera as√≠ncrono. Estos casos parecen tan raros que dif√≠cilmente se justifica dise√Īar la API principalmente alrededor de ellos. Si se puede hacer de alguna manera , creo que es suficiente


No tengo ninguna duda de que el equipo de React es consciente de la confusión que la naturaleza asíncrona de setState suele presentar, por lo que sospecho que hay otra muy buena razón para la semántica actual. Dime más :)

Discussion

Comentario m√°s √ļtil

As√≠ que aqu√≠ tienes algunas ideas. Esta no es una respuesta completa de ninguna manera, pero tal vez sea a√ļn m√°s √ļtil que no decir nada.

En primer lugar, creo que estamos de acuerdo en que es beneficioso retrasar la conciliación para realizar actualizaciones por lotes. Es decir, estamos de acuerdo en que setState() volver a renderizar sincrónicamente sería ineficaz en muchos casos, y es mejor realizar actualizaciones por lotes si sabemos que es probable que obtengamos varias.

Por ejemplo, si estamos dentro de un navegador click controlador, y tanto Child como Parent llaman setState , no queremos volver a renderizar los Child dos veces y, en su lugar, prefieren marcarlos como sucios y volver a renderizarlos juntos antes de salir del evento del navegador.

Usted pregunta: ¬Ņpor qu√© no podemos hacer exactamente lo mismo (procesamiento por lotes) pero escribimos setState actualizaciones inmediatamente a this.state sin esperar el final de la reconciliaci√≥n? No creo que haya una respuesta obvia (cualquiera de las soluciones tiene compensaciones) pero aqu√≠ hay algunas razones en las que puedo pensar.

Garantizar la coherencia interna

Incluso si state se actualiza sincrónicamente, props no lo son. (No puede saber props hasta que vuelva a renderizar el componente principal, y si lo hace sincrónicamente, el procesamiento por lotes desaparece de la ventana).

En este momento, los objetos proporcionados por React ( state , props , refs ) son internamente coherentes entre s√≠. Esto significa que si solo usa esos objetos, se garantiza que se referir√°n a un √°rbol completamente reconciliado (incluso si es una versi√≥n anterior de ese √°rbol). ¬ŅPor qu√© importa esto?

Cuando usa solo el estado, si se vacía sincrónicamente (como propuso), este patrón funcionaría:

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

Sin embargo, digamos que este estado debe levantarse para que se comparta entre algunos componentes, de modo que lo mueva a un padre:

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

Quiero resaltar que en las aplicaciones t√≠picas de React que se basan en setState() este es el tipo m√°s com√ļn de refactorizaci√≥n espec√≠fica de React que har√≠a a diario .

Sin embargo, ¡esto rompe nuestro código!

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

Esto se debe a que, en el modelo que propuso, this.state se eliminar√≠a inmediatamente, pero this.props no. Y no podemos vaciar inmediatamente this.props sin volver a renderizar el padre, lo que significa que tendr√≠amos que renunciar al procesamiento por lotes (que, seg√ļn el caso, puede degradar el rendimiento de manera muy significativa).

Tambi√©n hay casos m√°s sutiles de c√≥mo esto puede romperse, por ejemplo, si est√° mezclando datos de props (a√ļn no vac√≠os) y state (propuestos para ser eliminados inmediatamente) para crear un nuevo estado : https://github.com/facebook/react/issues/122#issuecomment -81856416. Las referencias presentan el mismo problema: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Estos ejemplos no son en absoluto teóricos. De hecho, los enlaces de React Redux solían tener exactamente este tipo de problema porque mezclan los accesorios de React con el estado que no es de 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.

No sé por qué los usuarios de MobX no se han topado con esto, pero mi intuición es que pueden estar tropezando con tales escenarios, pero los consideran su propia culpa. O tal vez no leen tanto de props y en su lugar leen directamente de los objetos mutables de MobX.

Entonces, ¬Ņc√≥mo resuelve React esto hoy? En React, tanto this.state como this.props actualizan solo despu√©s de la reconciliaci√≥n y el vaciado, por lo que ver√° que 0 se imprimen antes y despu√©s de la refactorizaci√≥n. Esto hace que el estado de elevaci√≥n sea seguro.

Sí, esto puede resultar inconveniente en algunos casos. Especialmente para personas que provienen de más entornos OO que solo quieren cambiar de estado varias veces en lugar de pensar cómo representar una actualización de estado completa en un solo lugar. Puedo empatizar con eso, aunque creo que mantener concentradas las actualizaciones de estado es más claro desde una perspectiva de depuración: https://github.com/facebook/react/issues/122#issuecomment -19888472.

A√ļn as√≠, tiene la opci√≥n de mover el estado que desea leer inmediatamente a alg√ļn objeto mutable lateralmente, especialmente si no lo usa como una fuente de verdad para renderizar. Que es m√°s o menos lo que MobX te permite hacer ūüôā.

Tambi√©n tiene la opci√≥n de vaciar todo el √°rbol si sabe lo que est√° haciendo. La API se llama ReactDOM.flushSync(fn) . No creo que lo hayamos documentado todav√≠a, pero definitivamente lo haremos en alg√ļn momento durante el ciclo de lanzamiento 16.x. Tenga en cuenta que, en realidad, obliga a volver a renderizar por completo para las actualizaciones que ocurren dentro de la llamada, por lo que debe usarlo con moderaci√≥n. De esta forma no se rompe la garant√≠a de consistencia interna entre props , state y refs .

En resumen, el modelo React no siempre conduce al código más conciso, pero es internamente consistente y garantiza que el estado de elevación sea seguro .

Habilitación de actualizaciones simultáneas

Conceptualmente, React se comporta como si tuviera una √ļnica cola de actualizaci√≥n por componente. Es por eso que la discusi√≥n tiene sentido: discutimos si aplicar actualizaciones a this.state inmediatamente o no porque no tenemos dudas de que las actualizaciones se aplicar√°n en ese orden exacto. Sin embargo, ese no tiene por qu√© ser el caso ( jaja ).

Recientemente, hemos estado hablando mucho sobre la "representación asíncrona". Admito que no hemos hecho un buen trabajo comunicando lo que eso significa, pero esa es la naturaleza de la I + D: persigues una idea que parece conceptualmente prometedora, pero realmente comprendes sus implicaciones solo después de haber pasado suficiente tiempo con ella.

Una forma en la que hemos estado explicando la "representación asíncrona" es que React podría asignar diferentes prioridades a las llamadas setState() dependiendo de dónde vengan: un controlador de eventos, una respuesta de red, una animación, etc.

Por ejemplo, si está escribiendo un mensaje, setState() llamadas en el TextBox necesidad de componentes a ser eliminados inmediatamente. Sin embargo, si recibe un nuevo mensaje mientras escribe , probablemente sea mejor retrasar la reproducción del nuevo MessageBubble hasta un cierto umbral (por ejemplo, un segundo) que dejar que la escritura entrecortada debido al bloqueo del hilo.

Si permitimos que ciertas actualizaciones tengan "menor prioridad", podr√≠amos dividir su renderizado en peque√Īos fragmentos de unos pocos milisegundos para que el usuario no los note.

S√© que optimizaciones de rendimiento como esta pueden no parecer muy emocionantes o convincentes. Podr√≠a decir: "No necesitamos esto con MobX, nuestro seguimiento de actualizaciones es lo suficientemente r√°pido como para evitar que se vuelvan a renderizar". No creo que sea cierto en todos los casos (por ejemplo, no importa qu√© tan r√°pido sea MobX, todav√≠a tiene que crear nodos DOM y hacer el renderizado para las vistas reci√©n montadas). A√ļn as√≠, si fuera cierto, y si conscientemente decidiera que est√° de acuerdo con incluir siempre objetos en una biblioteca JavaScript espec√≠fica que rastrea las lecturas y escrituras, tal vez no se beneficie tanto de estas optimizaciones.

Pero el renderizado asincrónico no se trata solo de optimizaciones de rendimiento.

Por ejemplo, considere el caso en el que navega de una pantalla a otra. Por lo general, mostraría una ruleta mientras se procesa la nueva pantalla.

Sin embargo, si la navegaci√≥n es lo suficientemente r√°pida (en un segundo m√°s o menos), parpadear y ocultar inmediatamente una ruleta provoca una experiencia de usuario degradada. Peor a√ļn, si tiene varios niveles de componentes con diferentes dependencias as√≠ncronas (datos, c√≥digo, im√°genes), terminar√° con una cascada de hilanderos que parpadean brevemente uno por uno. Esto es visualmente desagradable y hace que su aplicaci√≥n sea m√°s lenta en la pr√°ctica debido a todos los reflujos de DOM. Tambi√©n es la fuente de mucho c√≥digo repetitivo.

¬ŅNo ser√≠a bueno si cuando haces un setState() simple que genera una vista diferente, pudi√©ramos "comenzar" a generar la vista actualizada "en segundo plano"? Imagine que sin escribir ning√ļn c√≥digo de coordinaci√≥n usted mismo, podr√≠a elegir mostrar un indicador giratorio si la actualizaci√≥n tom√≥ m√°s de un cierto umbral (por ejemplo, un segundo) y, de lo contrario, dejar que React realice una transici√≥n sin problemas cuando las dependencias as√≠ncronas de todo el nuevo sub√°rbol est√©n satisfecho . Adem√°s, mientras estamos "esperando", la "pantalla antigua" permanece interactiva (por ejemplo, para que pueda elegir un elemento diferente al que realizar la transici√≥n), y React exige que, si tarda demasiado, debe mostrar una ruleta.

Resulta que, con el modelo React actual y algunos ajustes en los ciclos de vida , ¬°de hecho podemos implementar esto! @acdlite ha estado trabajando en esta funci√≥n durante las √ļltimas semanas y pronto publicar√° un RFC .

Tenga en cuenta que esto solo es posible porque this.state no se vacía inmediatamente. Si se eliminara inmediatamente, no tendríamos forma de comenzar a renderizar una "nueva versión" de la vista en segundo plano mientras la "versión anterior" todavía es visible e interactiva. Sus actualizaciones estatales independientes chocarían.

No quiero robarle el trueno a @acdlite con respecto a anunciar todo esto, pero espero que suene al menos un poco emocionante. Entiendo que esto todavía puede sonar como vaporware, o como si realmente no supiéramos lo que estamos haciendo. Espero que podamos convencerlo de lo contrario en los próximos meses y que apreciará la flexibilidad del modelo React. Y hasta donde tengo entendido, al menos en parte, esta flexibilidad es posible gracias a que no se descargan las actualizaciones de estado de inmediato.

Todos 31 comentarios

Todos estamos esperando @gaearon .

@Kaybarax Oye, es fin de semana ;-)

@mweststrate ¬°Oh! mi error. Frio.

Voy a arriesgarme aquí y decir que se debe a la agrupación de varios setState s en el mismo tick.

Me iré de vacaciones la semana que viene, pero probablemente iré el martes, así que intentaré responder el lunes.

function enqueueUpdate (componente) {
asegurarInyectado ();

// Varias partes de nuestro código (como ReactCompositeComponent's
// _renderValidatedComponent) asume que las llamadas a renderizar no est√°n anidadas;
// verifica que ese sea el caso. (Esto es llamado por cada actualización de nivel superior
// función, como setState, forceUpdate, etc .; creación y
// La destrucción de los componentes de nivel superior está protegida en ReactMount.)

if (! batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates (enqueueUpdate, componente);
regreso;
}

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

@mweststrate solo 2 centavos: esa es una pregunta muy v√°lida.
Estoy seguro de que todos estamos de acuerdo en que sería mucho más fácil razonar sobre el estado si setState fuera sincrónico.
Cualesquiera que hayan sido las razones para hacer que setState sea asincrónico, no estoy seguro de que el equipo reaccione bien en comparación con los inconvenientes que introduciría, por ejemplo, la dificultad para razonar sobre el estado actual y la confusión que genera a los desarrolladores.

Personalmente he tenido y he visto en otros desarrolladores confusión sobre este tema. @gaearon , sería genial obtener una explicación para esto cuando tengas algo de tiempo :)

Lo siento, es fin de a√Īo y nos hemos retrasado un poco en GitHub, etc. tratando de concluir todo en lo que hemos estado trabajando antes de las vacaciones.

Tengo la intenci√≥n de volver a este hilo y discutirlo. Pero tambi√©n es un objetivo en movimiento porque actualmente estamos trabajando en funciones as√≠ncronas de React que se relacionan directamente con c√≥mo y cu√°ndo se actualiza this.state . No quiero perder mucho tiempo escribiendo algo y luego tener que reescribirlo porque las suposiciones subyacentes han cambiado. As√≠ que me gustar√≠a mantener esto abierto, pero a√ļn no s√© cu√°ndo podr√© dar una respuesta definitiva.

As√≠ que aqu√≠ tienes algunas ideas. Esta no es una respuesta completa de ninguna manera, pero tal vez sea a√ļn m√°s √ļtil que no decir nada.

En primer lugar, creo que estamos de acuerdo en que es beneficioso retrasar la conciliación para realizar actualizaciones por lotes. Es decir, estamos de acuerdo en que setState() volver a renderizar sincrónicamente sería ineficaz en muchos casos, y es mejor realizar actualizaciones por lotes si sabemos que es probable que obtengamos varias.

Por ejemplo, si estamos dentro de un navegador click controlador, y tanto Child como Parent llaman setState , no queremos volver a renderizar los Child dos veces y, en su lugar, prefieren marcarlos como sucios y volver a renderizarlos juntos antes de salir del evento del navegador.

Usted pregunta: ¬Ņpor qu√© no podemos hacer exactamente lo mismo (procesamiento por lotes) pero escribimos setState actualizaciones inmediatamente a this.state sin esperar el final de la reconciliaci√≥n? No creo que haya una respuesta obvia (cualquiera de las soluciones tiene compensaciones) pero aqu√≠ hay algunas razones en las que puedo pensar.

Garantizar la coherencia interna

Incluso si state se actualiza sincrónicamente, props no lo son. (No puede saber props hasta que vuelva a renderizar el componente principal, y si lo hace sincrónicamente, el procesamiento por lotes desaparece de la ventana).

En este momento, los objetos proporcionados por React ( state , props , refs ) son internamente coherentes entre s√≠. Esto significa que si solo usa esos objetos, se garantiza que se referir√°n a un √°rbol completamente reconciliado (incluso si es una versi√≥n anterior de ese √°rbol). ¬ŅPor qu√© importa esto?

Cuando usa solo el estado, si se vacía sincrónicamente (como propuso), este patrón funcionaría:

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

Sin embargo, digamos que este estado debe levantarse para que se comparta entre algunos componentes, de modo que lo mueva a un padre:

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

Quiero resaltar que en las aplicaciones t√≠picas de React que se basan en setState() este es el tipo m√°s com√ļn de refactorizaci√≥n espec√≠fica de React que har√≠a a diario .

Sin embargo, ¡esto rompe nuestro código!

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

Esto se debe a que, en el modelo que propuso, this.state se eliminar√≠a inmediatamente, pero this.props no. Y no podemos vaciar inmediatamente this.props sin volver a renderizar el padre, lo que significa que tendr√≠amos que renunciar al procesamiento por lotes (que, seg√ļn el caso, puede degradar el rendimiento de manera muy significativa).

Tambi√©n hay casos m√°s sutiles de c√≥mo esto puede romperse, por ejemplo, si est√° mezclando datos de props (a√ļn no vac√≠os) y state (propuestos para ser eliminados inmediatamente) para crear un nuevo estado : https://github.com/facebook/react/issues/122#issuecomment -81856416. Las referencias presentan el mismo problema: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Estos ejemplos no son en absoluto teóricos. De hecho, los enlaces de React Redux solían tener exactamente este tipo de problema porque mezclan los accesorios de React con el estado que no es de 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.

No sé por qué los usuarios de MobX no se han topado con esto, pero mi intuición es que pueden estar tropezando con tales escenarios, pero los consideran su propia culpa. O tal vez no leen tanto de props y en su lugar leen directamente de los objetos mutables de MobX.

Entonces, ¬Ņc√≥mo resuelve React esto hoy? En React, tanto this.state como this.props actualizan solo despu√©s de la reconciliaci√≥n y el vaciado, por lo que ver√° que 0 se imprimen antes y despu√©s de la refactorizaci√≥n. Esto hace que el estado de elevaci√≥n sea seguro.

Sí, esto puede resultar inconveniente en algunos casos. Especialmente para personas que provienen de más entornos OO que solo quieren cambiar de estado varias veces en lugar de pensar cómo representar una actualización de estado completa en un solo lugar. Puedo empatizar con eso, aunque creo que mantener concentradas las actualizaciones de estado es más claro desde una perspectiva de depuración: https://github.com/facebook/react/issues/122#issuecomment -19888472.

A√ļn as√≠, tiene la opci√≥n de mover el estado que desea leer inmediatamente a alg√ļn objeto mutable lateralmente, especialmente si no lo usa como una fuente de verdad para renderizar. Que es m√°s o menos lo que MobX te permite hacer ūüôā.

Tambi√©n tiene la opci√≥n de vaciar todo el √°rbol si sabe lo que est√° haciendo. La API se llama ReactDOM.flushSync(fn) . No creo que lo hayamos documentado todav√≠a, pero definitivamente lo haremos en alg√ļn momento durante el ciclo de lanzamiento 16.x. Tenga en cuenta que, en realidad, obliga a volver a renderizar por completo para las actualizaciones que ocurren dentro de la llamada, por lo que debe usarlo con moderaci√≥n. De esta forma no se rompe la garant√≠a de consistencia interna entre props , state y refs .

En resumen, el modelo React no siempre conduce al código más conciso, pero es internamente consistente y garantiza que el estado de elevación sea seguro .

Habilitación de actualizaciones simultáneas

Conceptualmente, React se comporta como si tuviera una √ļnica cola de actualizaci√≥n por componente. Es por eso que la discusi√≥n tiene sentido: discutimos si aplicar actualizaciones a this.state inmediatamente o no porque no tenemos dudas de que las actualizaciones se aplicar√°n en ese orden exacto. Sin embargo, ese no tiene por qu√© ser el caso ( jaja ).

Recientemente, hemos estado hablando mucho sobre la "representación asíncrona". Admito que no hemos hecho un buen trabajo comunicando lo que eso significa, pero esa es la naturaleza de la I + D: persigues una idea que parece conceptualmente prometedora, pero realmente comprendes sus implicaciones solo después de haber pasado suficiente tiempo con ella.

Una forma en la que hemos estado explicando la "representación asíncrona" es que React podría asignar diferentes prioridades a las llamadas setState() dependiendo de dónde vengan: un controlador de eventos, una respuesta de red, una animación, etc.

Por ejemplo, si está escribiendo un mensaje, setState() llamadas en el TextBox necesidad de componentes a ser eliminados inmediatamente. Sin embargo, si recibe un nuevo mensaje mientras escribe , probablemente sea mejor retrasar la reproducción del nuevo MessageBubble hasta un cierto umbral (por ejemplo, un segundo) que dejar que la escritura entrecortada debido al bloqueo del hilo.

Si permitimos que ciertas actualizaciones tengan "menor prioridad", podr√≠amos dividir su renderizado en peque√Īos fragmentos de unos pocos milisegundos para que el usuario no los note.

S√© que optimizaciones de rendimiento como esta pueden no parecer muy emocionantes o convincentes. Podr√≠a decir: "No necesitamos esto con MobX, nuestro seguimiento de actualizaciones es lo suficientemente r√°pido como para evitar que se vuelvan a renderizar". No creo que sea cierto en todos los casos (por ejemplo, no importa qu√© tan r√°pido sea MobX, todav√≠a tiene que crear nodos DOM y hacer el renderizado para las vistas reci√©n montadas). A√ļn as√≠, si fuera cierto, y si conscientemente decidiera que est√° de acuerdo con incluir siempre objetos en una biblioteca JavaScript espec√≠fica que rastrea las lecturas y escrituras, tal vez no se beneficie tanto de estas optimizaciones.

Pero el renderizado asincrónico no se trata solo de optimizaciones de rendimiento.

Por ejemplo, considere el caso en el que navega de una pantalla a otra. Por lo general, mostraría una ruleta mientras se procesa la nueva pantalla.

Sin embargo, si la navegaci√≥n es lo suficientemente r√°pida (en un segundo m√°s o menos), parpadear y ocultar inmediatamente una ruleta provoca una experiencia de usuario degradada. Peor a√ļn, si tiene varios niveles de componentes con diferentes dependencias as√≠ncronas (datos, c√≥digo, im√°genes), terminar√° con una cascada de hilanderos que parpadean brevemente uno por uno. Esto es visualmente desagradable y hace que su aplicaci√≥n sea m√°s lenta en la pr√°ctica debido a todos los reflujos de DOM. Tambi√©n es la fuente de mucho c√≥digo repetitivo.

¬ŅNo ser√≠a bueno si cuando haces un setState() simple que genera una vista diferente, pudi√©ramos "comenzar" a generar la vista actualizada "en segundo plano"? Imagine que sin escribir ning√ļn c√≥digo de coordinaci√≥n usted mismo, podr√≠a elegir mostrar un indicador giratorio si la actualizaci√≥n tom√≥ m√°s de un cierto umbral (por ejemplo, un segundo) y, de lo contrario, dejar que React realice una transici√≥n sin problemas cuando las dependencias as√≠ncronas de todo el nuevo sub√°rbol est√©n satisfecho . Adem√°s, mientras estamos "esperando", la "pantalla antigua" permanece interactiva (por ejemplo, para que pueda elegir un elemento diferente al que realizar la transici√≥n), y React exige que, si tarda demasiado, debe mostrar una ruleta.

Resulta que, con el modelo React actual y algunos ajustes en los ciclos de vida , ¬°de hecho podemos implementar esto! @acdlite ha estado trabajando en esta funci√≥n durante las √ļltimas semanas y pronto publicar√° un RFC .

Tenga en cuenta que esto solo es posible porque this.state no se vacía inmediatamente. Si se eliminara inmediatamente, no tendríamos forma de comenzar a renderizar una "nueva versión" de la vista en segundo plano mientras la "versión anterior" todavía es visible e interactiva. Sus actualizaciones estatales independientes chocarían.

No quiero robarle el trueno a @acdlite con respecto a anunciar todo esto, pero espero que suene al menos un poco emocionante. Entiendo que esto todavía puede sonar como vaporware, o como si realmente no supiéramos lo que estamos haciendo. Espero que podamos convencerlo de lo contrario en los próximos meses y que apreciará la flexibilidad del modelo React. Y hasta donde tengo entendido, al menos en parte, esta flexibilidad es posible gracias a que no se descargan las actualizaciones de estado de inmediato.

Maravillosa explicación en profundidad de las decisiones detrás de la arquitectura de React. Gracias.

Marcos

Gracias, Dan.

Yo ‚̧ԳŹ este problema. Pregunta impresionante y respuesta impresionante. Siempre pens√© que esta era una mala decisi√≥n de dise√Īo, ahora tengo que repensar ūüėĄ

Gracias, Dan.

Yo lo llamo asyncAwesome setState: smile:

Tiendo a pensar que todo debe implementarse de forma asíncrona primero, y si encuentra la necesidad de una operación de sincronización, bueno, envuelva la operación asíncrona con una espera para su finalización. Es mucho más fácil hacer un código de sincronización a partir de un código asincrónico (todo lo que necesita es una envoltura) que al revés (que básicamente requiere una reescritura completa, a menos que alcance el subproceso, que no es para nada liviano).

@gaearon ¡ gracias por la extensa explicación! Me ha estado molestando durante mucho tiempo ("debe haber una buena razón, pero nadie sabe cuál"). Pero ahora tiene mucho sentido y veo cómo esta es una decisión realmente consciente :). Muchas gracias por la extensa respuesta, ¡realmente lo agradezco!

O tal vez no leen tanto de los accesorios y en su lugar leen directamente de los objetos mutables de MobX.

Creo que esto es bastante cierto, en MobX, los accesorios se usan generalmente como configuración de componentes, y los datos del dominio generalmente no se capturan en accesorios, sino en entidades de dominio que se pasan entre componentes.

Nuevamente, muchas gracias!

@gaearon Gracias por la gran y detallada explicación.
Aunque todavía falta algo aquí que creo que entiendo, pero quiero estar seguro.

Cuando el evento se registra como "Reacci√≥n externa", eso significa que tal vez a trav√©s de addEventListener en una referencia, por ejemplo. Entonces no tiene lugar ning√ļn procesamiento por lotes.
Considere este código:

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>
    );
  }
}

Cuando hagamos clic en el botón "React Event", veremos en la consola:
"before setState" 0
"after setState" 0
Cuando se haga clic en el otro botón "Evento DOM directo", veremos en la consola:
"before setState" 0
"after setState" 1

Despu√©s de una peque√Īa investigaci√≥n y navegar por el c√≥digo fuente, creo que s√© por qu√© sucede esto.
Reaccionar no puede controlar completamente el flujo del evento y no puede estar seguro de cuándo y cómo se activará el próximo evento, por lo que como un "modo de pánico" simplemente activará el cambio de estado inmediatamente.

¬ŅQu√© piensas sobre esto? :pensando:

@ sag1v, aunque un poco relacionado, probablemente sea m√°s claro abrir un nuevo n√ļmero para nuevas preguntas. Simplemente use # 11527 en alg√ļn lugar de la descripci√≥n para vincularlo a este.

@ sag1v @gaearon me había dado una respuesta muy concisa aquí https://twitter.com/dan_abramov/status/949992957180104704 . Creo que su opinión sobre esto también me respondería de manera más concreta.

@mweststrate Pens√© en abrir un nuevo n√ļmero, pero luego me di cuenta de que esto est√° directamente relacionado con su pregunta "¬Ņpor qu√© setState asincr√≥nico?".
Como esta discusión trata sobre las decisiones tomadas para hacer setState "async", pensé en agregar cuándo y por qué hacerlo "sincronizado".
No me importa abrir un nuevo n√ļmero si no te convenc√≠ de que mi publicaci√≥n est√° relacionada con este problema: gui√Īo:

@Kaybarax Eso porque tu pregunta fue "_¬ŅCu√°ndo est√° sincronizado_" y no "_ ¬ŅPor qu√© est√° sincronizado_" ?.
Como mencioné en mi publicación, creo que sé el por qué, pero quiero estar seguro y obtener la respuesta oficial jeje. :sonrisa:

Reaccionar no puede controlar completamente el flujo del evento y no puede estar seguro de cuándo y cómo se desencadenará el próximo evento, por lo que, como un "modo de pánico", solo activará el cambio de estado de inmediato.

Algo así como. Aunque esto no está exactamente relacionado con la pregunta sobre la actualización de this.state .

Lo que está preguntando es en qué casos React habilita el procesamiento por lotes. React actualmente procesa actualizaciones por lotes dentro de los controladores de eventos administrados por React porque React "se sienta" en el marco de la pila de llamadas superior y sabe cuándo se han ejecutado todos los controladores de eventos de React. En ese momento, descarga la actualización.

Si React no configura el controlador de eventos, actualmente hace que la actualización sea sincrónica. Porque no sabe si es seguro esperar o no, y si pronto se realizarán otras actualizaciones.

En las versiones futuras de React, este comportamiento cambiará. El plan es tratar las actualizaciones como de baja prioridad de forma predeterminada para que terminen unidas y agrupadas (por ejemplo, en un segundo), con la opción de aceptarlas para eliminarlas de inmediato. Puede leer más aquí: https://github.com/facebook/react/issues/11171#issuecomment -357945371.

¬°Impresionante!

Esa pregunta y respuesta deben documentarse en alg√ļn lugar m√°s accesible. Gracias chicos por iluminarnos.

Aprendió mucho . Gracias

Tratando de agregar mi punto de vista al hilo. Trabajo en una aplicaci√≥n basada en MobX algunos meses, he estado explorando ClojureScript durante a√Īos e hice mi propia alternativa de React (llamada Respo), prob√© Redux en los primeros d√≠as aunque en muy poco tiempo, y apuesto por ReasonML.

La idea central al combinar React y Functional Programming (FP) es que tienes un dato que puedes convertir en una vista con cualquier habilidad que tengas que obedezca las leyes de FP. No tiene efectos secundarios si solo usa funciones puras.

Reaccionar no es puramente funcional. Al adoptar los estados locales dentro de los componentes, React tiene el poder de interactuar con varias bibliotecas relacionadas con DOM y otras API de navegador (también amigables con MobX), lo que, mientras tanto, hace que React sea impuro. Sin embargo, probé en ClojureScript, si React es puro, podría ser un desastre ya que es realmente difícil interactuar con tantas bibliotecas existentes que tienen efectos secundarios.

Entonces, en Respo (mi propia soluci√≥n), ten√≠a dos objetivos que parecen estar en conflicto, 1) view = f(store) por lo que no se espera un estado local; 2) No me gusta programar todos los estados de la interfaz de usuario de los componentes en los reductores globales, ya que podr√≠a ser dif√≠cil de mantener. Al final, descubr√≠ que necesito un az√ļcar de sintaxis, lo que me ayuda a mantener los estados de los componentes en una tienda global con paths , mientras escribo actualizaciones de estado dentro del componente con macros de Clojure.

Entonces, lo que aprend√≠: los estados locales es una caracter√≠stica de la experiencia del desarrollador, debajo queremos estados globales que permitan a nuestros motores realizar optimizaciones en niveles profundos. Entonces, la gente de MobX prefiere OOP, ¬Ņes eso para la experiencia del desarrollador o para los motores?

Por cierto, di una charla sobre el futuro de React y sus funciones asincrónicas, por si te lo perdiste:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Dan, eres mi ídolo ..... muchas gracias.

¬ŅFue √ļtil esta p√°gina
0 / 5 - 0 calificaciones