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