Escribir interfaces de usuario fluidas se vuelve cada vez más desafiante a medida que aumenta la complejidad de la aplicación. En esta charla, exploraremos cómo una programación adecuada mejora la experiencia de tu aplicación al adentrarnos en algunas de las características concurrentes de React, comprendiendo sus fundamentos y cómo funcionan bajo el capó.
Profundizando en Concurrent React
Video Summary and Transcription
La charla trató sobre Concurrent React y su impacto en el rendimiento de la aplicación, especialmente en relación con tareas largas en el hilo principal. Se exploró la paralelización con workers y los desafíos de WebAssembly para tareas de interfaz de usuario. Se cubrieron los conceptos de concurrencia, programación y renderizado, junto con técnicas para optimizar el rendimiento y abordar los renders desperdiciados. La charla también destacó los beneficios de las mejoras en la hidratación y el nuevo perfilador en Concurrent React, y mencionó futuras mejoras como React fetch y primitivas de programación nativas. Se enfatizó la importancia de comprender los aspectos internos de React y correlacionar las métricas de rendimiento con las métricas empresariales.
1. Introducción a Concurrent React
Hola, React Advanced. Estamos aquí para hablar sobre Concurrent React, adentrándonos en sus entresijos. Si te gustan las charlas de Deja, disfrutarás de esta. Soy un ingeniero front-end en Medallia y también soy voluntario en TechLabs. Comencemos resumiendo Concurrent React en una palabra o expresión. Comparte tus ideas usando el código QR proporcionado.
Hola, React Advanced. Es genial estar aquí, finalmente, en una de mis conferencias favoritas, así que gracias por invitarme. Estamos aquí para hablar sobre Concurrent React. Supongo que será la segunda charla hasta la fecha que discuta un poco de los entresijos de React. Si te gustan las charlas de Deja, probablemente te gustará esta. Esperemos que sea tan divertida como esa.
Este soy yo, soy un ingeniero front-end en Medallia. También soy voluntario en TechLabs, y me puedes encontrar en todas partes como widecombinator. Por cierto, todos los enlaces de esta sesión, incluyendo las diapositivas, están disponibles en este código QR, así que si quieres seguir. Un aviso rápido, se supone que vamos a adentrarnos en los detalles aquí, así que cuando sientas que algún contenido necesita más discusiones o explicaciones, busca este emoji, significa que vamos a tener más discusiones.
Genial, me gustaría comenzar preguntándoles: si tuvieran que resumir Concurrent React en una palabra o expresión, ¿con qué irían? Por ejemplo, vimos las charlas de Tejas y vimos que las fibras son unidades de trabajo, así que si tuvieran que hacer un ejercicio similar con Concurrent React, ¿qué harían? Para eso, realmente quiero contar con su ayuda, así que este es el código QR para que ingresen sus opiniones, y tengo 30 segundos, así que sí, me encantaría saber qué piensan sobre Concurrent React, una palabra, una expresión, de qué se trata, y sí, es gracioso porque 40 segundos suena como mucho tiempo, pero cuando tienes que entablar una conversación en el ínterin.
2. Impacto de las Tareas Largas en el Hilo Principal
Hablemos sobre el hilo principal y el impacto de las tareas largas en nuestras aplicaciones. A menudo vemos estas tareas bloqueando el hilo principal, causando falta de respuesta y frustración para los usuarios, como hacer clic repetidamente. Las métricas e investigaciones muestran que el retraso en la primera interacción puede ser siete veces peor en dispositivos móviles, y las tareas largas pueden retrasar el TTI hasta doce veces en dispositivos móviles. En dispositivos antiguos, la mitad del tiempo de carga puede dedicarse a tareas largas, lo que afecta negativamente las tasas de conversión. Para evitar bloquear el hilo principal, exploremos diferentes estrategias de ejecución de tareas.
3. Parallelismo con Workers
Vamos a explorar el paralelismo en el navegador utilizando workers. Los workers tienen algunas consideraciones, como el acceso limitado a variables y al DOM. Podemos utilizar actores o memoria compartida como abstracciones para los workers. Los actores son propietarios completos de sus datos y se comunican a través de mensajes. Sin embargo, postMessage es un mecanismo de enviar y olvidar sin seguimiento incorporado de solicitudes y respuestas. La memoria compartida, como el búfer de matriz compartida, permite el acceso directo a la memoria, mejorando la eficiencia de la comunicación.
Entonces, comencemos por el otro enfoque, el paralelismo. El paralelismo, como probablemente sabes, ocurre en los navegadores con los workers. Los workers tienen algunas consideraciones. El intercambio de datos se realiza mediante el envío de mensajes. Tenemos esa cosa llamada postMessage que probablemente conozcas. Pero la primera consideración de los workers es que no tenemos acceso a las variables ni al código del hilo que los creó. Y tampoco tenemos acceso al DOM. Por lo tanto, realizar cambios en la interfaz de usuario desde un worker es realmente complicado y a veces incluso imposible. Pero tenemos dos abstracciones con las que podríamos trabajar al pensar en los workers. Tenemos actores y memoria compartida. La primera de ellas, los actores, probablemente hayas oído hablar de actores en otros lenguajes como Elixir u otros, especialmente en el backend, es realmente importante. Un actor es una abstracción donde cada actor es propietario completo de losdata en los que opera y solo ve, envía y recibe mensajes, eso es prácticamente todo. Y en el navegador, podemos pensar, por ejemplo, que el hilo principal es el actor que es propietario del DOM y de la interfaz de usuario. Pero la primera consideración es que postMessage es un mecanismo de enviar y olvidar. Por lo tanto, no tiene ningún entendimiento incorporado de solicitudes, respuestas y seguimiento de eso. Y la segunda cosa es que, bien, descargamos el código del hilo principal para que las cosas sean más rápidas. Pero al mismo tiempo, esta comunicación, porque ocurre mediante copias, tiene una sobrecarga de comunicación. Por lo tanto, debemos equilibrar eso y también el worker al que enviamos las cosas podría estar ocupado, por lo que eso es otra cosa que debemos tener en cuenta. Por otro lado, tenemos memoria compartida. Y en el navegador, tenemos un tipo llamado búfer de matriz compartida. Y eso es realmente genial porque, por ejemplo, si enviamos un búfer de matriz compartida mediante postMessage. En el otro extremo, obtendrás un identificador para el mismo fragmento de memoria. Eso es
4. Concurrencia y Programación en Concurrent React
Debido a la forma en que se construyó la web y los navegadores, no existen API integradas para el acceso concurrente. Los ingenieros frontend a menudo encuentran que WebAssembly (WASM) es desafiante y más lento al realizar tareas relacionadas con la interfaz de usuario. Los workers son excelentes para el procesamiento de datos pero difíciles para la interfaz de usuario. La segunda parte se centra en la concurrencia y la programación, explorando heurísticas, niveles de prioridad y carriles de renderizado. React sigue un modelo de multitarea cooperativa con un único hilo de renderizado interoperable, lo que permite la renderización intercalada y las actualizaciones en segundo plano sin bloquear la entrada del usuario.
5. Heurísticas, Niveles de Prioridad y Carriles de Renderizado
React utiliza la ejecución de vuelta al hilo principal cada 5 milisegundos, lo que hace que el renderizado sea interoperable. Los niveles de prioridad van desde inmediato hasta inactivo, determinando cuándo se deben realizar las tareas. Los carriles de renderizado, construidos alrededor de máscaras de bits, permiten el renderizado por lotes y reducen la sobrecarga. Estos conceptos pueden beneficiar a los ingenieros frontend al manejar una gran cantidad de datos.
Otra cosa son los niveles de prioridad. Los vemos en el código fuente del planificador. Pero los vemos repetidos en todo el framework. Los vemos en el paquete reconciliador, los vemos en los renderizadores como React.non, e incluso los vemos en las herramientas de desarrollo. Básicamente, van desde inmediato hasta inactivo. Y cada uno de ellos tiene diferentes prioridades asignadas que básicamente le dirán a React cuándo se debe hacer algo. Por último, pero no menos importante, los carriles de renderizado, que también son una abstracción asombrosa. Y si estuvieras enseñando estas charlas, probablemente te estarías preguntando de qué se trata esa parte del código. Los carriles de renderizado son una abstracción construida alrededor de máscaras de bits. Cada carril ocupa un bit en una máscara de bits. Y en React, cada actualización se asigna a un carril. Y debido a eso, las actualizaciones en el mismo carril se renderizan en el mismo lote y en carriles diferentes, obtienes diferentes lotes. Y lo primero bueno que obtienes es que, al ser una máscara de bits, tienes 31 niveles de granularidad. Y lo otro asombroso es que básicamente permiten que React elija si ejecutar múltiples transiciones en un solo lote o en lotes separados, y todo esto reduce la sobrecarga de tener múltiples pasadas de diseño, múltiples recálculos de estilos y múltiples pintados en el navegador. Eso fue mucho, ¿verdad? Y yo mismo, cuando pasé por algunos de estos conceptos, estaba realmente, realmente impresionado, todo era asombroso y realmente interesante, pero al mismo tiempo no podía dejar de recordar esta charla de Kaiser. Se llama Pero tú no eres Facebook. Así que no estamos construyendo planificadores, no estamos haciendo ese tipo de cosas a diario. Entonces, ¿cómo podemos, como el otro 99% de los ingenieros frontend, beneficiarnos de esto en proyectos cotidianos? Esto nos lleva a la próxima parte, que es la programación en React para el resto de nosotros. Nuevamente, hay muchos, muchos escenarios donde creo que cada una de las características concurrentes puede ser asombrosa, pero aquí he agrupado cuatro de ellas. Y el primero es cuando tenemos que manejar una gran cantidad de datos. Admito que muchos de ellos, vimos muchos ejemplos que en el primer momento parecen no prácticos. Por ejemplo, vemos cómo encontrar números primos en nuestros métodos aleatorios y actualizar eso y optimizar eso con la transición, o cómo podemos ejecutar algoritmos complejos para descifrar contraseñas o incluso renderizar cosas enormes. Y esos ejemplos son geniales para hacer pruebas de rendimiento y mostrar lo que puedes hacer con React concurrente. Pero al mismo tiempo, es importante para nosotros recordar que nosotros, los ingenieros frontend, renderizamos una gran cantidad de puntos de datos, por ejemplo, en cosas.
6. Rendering Large Amounts of Data
El renderizado de grandes cantidades de datos, como en un panel de control, puede causar animaciones lentas. Mediante el uso de transiciones, las animaciones pueden ser suaves y receptivas, independientemente del tamaño de los datos.
O a veces tenemos que renderizar cosas en un lienzo y no tenemos un lienzo fuera de pantalla disponible. O a veces simplemente tenemos que procesar una gran cantidad de data. Por ejemplo, un panel de control. Por lo general, estamos construyendo paneles de control, por ejemplo. Y en este, básicamente estoy renderizando la cantidad de visitantes por día en un sitio web. Y como puedes ver, la animación es un poco lenta debido a la cantidad de data que estoy actualizando. Y no hay mucha magia en este componente. Tengo un efecto simple, tengo algún estado y un controlador de no cambio. Así que si cambio eso para usar una transición, podemos ver que, por ejemplo, primero la animación siempre es suave sin importar qué, y también sin importar cuántos data tenga, es receptiva.
7. Optimizing Performance with Transitions
Cuando vi por primera vez las transiciones, me di cuenta de su potencial para optimizar el rendimiento en varios escenarios. Por ejemplo, en una aplicación con una gran cantidad de puntos de datos trazados en un mapa, utilizamos workers y Redux Saga para manejar la búsqueda, filtrado y optimización de datos. De manera similar, en un panel de administración de juegos con miles de jugadores enviando mensajes, tuvimos que virtualizar listas y utilizar la memorización de manera extensiva. Sin embargo, las transiciones podrían haber mejorado en gran medida el proceso de optimización.
8. Tackling Wasted Renders with External Store Hooks
Para abordar las representaciones desperdiciadas, podemos usar ganchos de almacenamiento externo como el uso del selector de historial. Al crear selectores para propiedades específicas, podemos minimizar las representaciones innecesarias, lo que resulta en un mejor rendimiento para aplicaciones a gran escala.
Otra cosa es abordar las representaciones desperdiciadas. Normalmente pensamos en el uso de devoluciones de llamada, el uso de eso tipo de cosas para abordar las representaciones desperdiciadas. O incluso, por ejemplo, cambiar las props que pasamos usando react.memo y ese tipo de cosas. Pero ¿quién aquí piensa en usar un gancho de almacenamiento externo? Wow, vale, genial. Es un gancho que se comercializó por primera vez como parte de React concurrente para los mantenedores de bibliotecas. Y de hecho, vimos que algunas bibliotecas de estado lo adoptaron. Por ejemplo, Redux mismo comenzó a usarlo desde V8, y Vultio y muchos otros. Pero esto planteó la pregunta de cómo podríamos usarlo. Una cosa que usamos mucho es React Router, ¿verdad? Entonces supongo que la mayoría de nosotros estamos usando React Router, sí, bastantes manos. Entonces probablemente conozcamos el uso de location. Es un gancho que podemos obtener, por ejemplo, el nombre de la ruta, el hash y otra información sobre nuestra ruta. Pero el uso de location es un gancho que devuelve demasiada información, incluso cuando solo necesitamos parte de ella. Y si lo usamos así, el resultado es, por ejemplo, que aquí puedes ver que aunque solo estoy actualizando el hash, los componentes del nombre de la ruta se volverán a representar porque estoy observando ese gancho. Entonces, ¿cómo podríamos cambiar eso? Podríamos volver a nuestro ejemplo original y reemplazarlo con un nuevo gancho llamado uso del selector de historial. Y creé el uso del selector de historial utilizando el uso del historial más el uso de almacenamiento externo sincronizado, y ahora puedo crear selectores para el nombre de la ruta y el hash. Y el resultado es que, al hacer clic ahora, solo el hash se vuelve a representar, por lo que he ahorrado algunas representaciones. Y este fue un ejemplo muy simple, pero a gran escala
9. Hydration and Concurrent React
Las mejoras en la hidratación permiten la hidratación selectiva y priorizar las partes de la página con las que los usuarios interactúan. Concurrent React permite una mayor interactividad al permitir que el navegador realice otras tareas simultáneamente. Esto resulta en un menor retraso en la primera entrada y una mejor experiencia de usuario. Empresas como Verso ya han implementado estas mejoras en el sitio web de Next.js.
10. New Profiler and Future Enhancements
Por último, el nuevo perfilador nos permite entender correctamente qué está sucediendo con nuestras aplicaciones. Proporciona una pestaña de programación y nuevas pistas para optimizar las transiciones. Cosas emocionantes están por venir, como bibliotecas de IO como React fetch, caché incorporada para componentes, suspense para árboles con carga de CPU, más hooks para los mantenedores de bibliotecas, componentes fuera de pantalla, componentes de servidor y primitivas de programación nativas en el navegador llamadas Prioritized Task Scheduling. Esta API basada en promesas, alineada con el trabajo del equipo central de React y otros equipos, ofrece una programación robusta y características interesantes como ejecución en el ciclo de eventos, programación de tareas y verificación de la entrada del navegador. Empresas como Airbnb y Facebook ya la están utilizando.
Por último, el nuevo perfilador. Porque no solo queremos construir aplicaciones, sino que también queremos entender correctamente qué está sucediendo con nuestras aplicaciones. Así que tenemos esta pestaña de programación que podemos usar, por ejemplo, para ver correctamente cómo van nuestras transiciones. Y una de mis partes favoritas son las nuevas pistas que tenemos. Por ejemplo, el perfilador puede ver que tenemos una tarea larga que potencialmente se podría mover a una transición. Y esto podría ser una optimización interesante. Y estoy realmente emocionado por todas estas cosas. Pero tengo que decir que estoy aún más emocionado por lo que está por venir. Así que vamos a tener bibliotecas de IO como React fetch. Ha estado ahí fuera durante un tiempo. En algún momento sucederá, supongo. Vamos a tener una caché incorporada para componentes que se integre con suspense. Una de mis partes favoritas es el suspense para árboles con carga de CPU. Así que si perfilas tu aplicación y descubres que alguna parte de tu árbol va a llevar mucho tiempo, puedes adelantarte y luego retroceder sin siquiera intentar renderizar. Eso es increíble. Vamos a tener más hooks para los mantenedores de bibliotecas, como usar la búsqueda de hecho. El componente fuera de pantalla, ese también es otro que me encanta, que básicamente es una forma de asignar prioridad ociosa como las que vimos antes en tu código a una parte de tu árbol. Componentes de servidor, que podrían ser muchos otros talleres solo para abordarlos. Y no es algo de React, pero algo que me emociona mucho es la programación nativa en el navegador. Entonces, ¿quién aquí ha visto este paraguas de API antes, se llama Prioritized Task Scheduling? Si no lo has hecho, definitivamente te recomendaría que lo revises. Pero básicamente es una forma más robusta de hacer programación en el navegador que, por ejemplo, solicitar devoluciones de llamada ociosas y cosas así. Y va a ser algo que está basado en promesas e integrado directamente en el ciclo de eventos. Y no solo eso, sino que está alineado con el trabajo del equipo central de React y otros equipos como Polymer, los chicos de Google Maps e incluso la comunidad de estándares web. Y esta API es un paraguas para muchas cosas interesantes, como APIs para usar la ejecución en el ciclo de eventos, APIs para programar tareas, APIs para verificar si el navegador está ocupado manejando algún tipo de entrada del usuario, etc. E incluso tenemos personas que la están utilizando. Así que tenemos, por ejemplo, Airbnb. Facebook, que fue uno de los principales contribuyentes para la planificación de la entrada, por ejemplo, también lo está haciendo. E incluso tenemos bibliotecas fuertemente inspiradas en la especificación de esa API. Y tenemos
11. Closing Notes and Takeaways
React no es reactivo, pero sí es concurrente. React ha llevado a otros frameworks hacia el futuro y a toda la web. El soporte de primera clase para promesas y la comprensión de los aspectos internos de React ayudan a crear abstracciones. La planificación no garantiza un mejor rendimiento. No hay una solución mágica. Correlaciona las métricas de rendimiento con las métricas de negocio. Las diapositivas están disponibles en mi perfil de Speaker Deck.
Para finalizar, React no es reactivo, pero sí es concurrente. React ha llevado a otros frameworks hacia el futuro y a toda la web. El soporte de primera clase para promesas y la comprensión de los aspectos internos de React ayudan a crear abstracciones. La planificación no garantiza un mejor rendimiento. No hay una solución mágica. Es importante correlacionar las métricas de rendimiento con las métricas de negocio. Las diapositivas de esta sesión están disponibles en mi perfil de Speaker Deck.
Quiero comenzar vinculando esta charla de Rich Harris. Me encanta esta charla, es una de las más brillantes que he visto en los últimos años. Se llama `Repensando la Reactividad`. En esta charla, él dice que React no es reactivo. Y sí, esto ha estado ahí fuera durante un tiempo y sí, tiene razón, no es reactivo debido a la naturaleza de la Virtual DOM y cómo funciona, pero es concurrente. Y eso podría ser suficiente para tu caso, Virtual DOM y muchas cosas se han optimizado lo suficiente para muchos casos, por lo que podría ser el tuyo. Otra cosa es esta cita de Guilherme Verso de hace un par de años cuando dijo que React era una idea tan increíble que pasaríamos el resto del día explorando sus implicaciones y aplicaciones. Y creo que, por ejemplo, el hecho de que React esté fuertemente relacionado con esta API de planificación, que es increíble, es una de esas señales. Así que React no solo ha llevado a otros frameworks hacia el futuro, sino también a toda la web y creo que es increíble ver eso. Para la próxima conclusión, ¿quién ha visto este reciente RFC que es como el tema candente en Twitter? Todo el mundo está hablando del soporte de primera clase para promesas y el uso de React, y algunas personas tienen muchas opiniones diferentes al respecto. Así que tengo un ejemplo muy simple de código. No pretendo que leas todo, pero básicamente lo que estoy haciendo aquí es crear una caché muy, muy simple y está en TypeScript y estoy lanzando promesas, etc. Pero creé un hook llamado usePromise en este código. Y podemos usar ese código, sé que el tamaño de la fuente no es el mejor aquí, pero básicamente tengo este retraso y estoy usando una promesa con cualquier promesa. Y el resultado es que se está suspendiendo, etc. Y cuando ves eso, puede sonar mucho como, okay, esto es el uso de React. Así que sí, esta es como, por supuesto, una versión mucho, mucho más simple de React use. Pero la razón por la que estoy mostrando esto es que realmente creo que entender esos aspectos internos y las razones detrás de ellos realmente nos ayuda a crear nuestras propias abstracciones. Y eso es realmente, realmente increíble. Y un ejemplo es el soporte de primera clase para promesas. Otra cosa es que la planificación no necesariamente significa un mejor rendimiento. Al igual que la reactividad o cualquier otra estrategia, como usar o no Virtual DOM, tiene sus inconvenientes. Por eso, siempre es importante tener en cuenta el cliché de que no hay una solución mágica. Y como no hay una solución mágica, es importante que identifiquemos nuestras métricas principales y lo que es realmente importante para nosotros. En estos días, hay mucha información por ahí, por lo que verás a personas increíbles construyendo herramientas increíbles con muchas opiniones increíbles. Y sé que es realmente, realmente fácil sentirse perdido entre tantas cosas diferentes, tantos pensamientos diferentes que parecen increíbles. Así que es importante para nosotros construir aplicaciones e incluso construir nuestra propia biblioteca a veces, que correlacionemos esas métricas de rendimiento con las métricas de negocio, como las tasas de conversión, etc., porque eso es lo que más importa la mayoría de las veces al final para nuestros usuarios.
Las diapositivas de esta sesión están disponibles en mi perfil de Speaker Deck. El enlace estaba en
12. Scheduling Concurrent React Questions
Si tienes opiniones sobre la programación de preguntas concurrentes de React, si quieres hablar más sobre ese rendimiento, estaré disponible no solo aquí, sino también en el stand de los oradores y en el evento. Muchas gracias por tenerme, React Advanced. ¡Gracias, gracias, gracias! Me gustaría saber, ¿tu interés en esto proviene de cosas que encontraste en el trabajo o es algo en lo que has estado interesado por tu cuenta? Fue una experiencia personal cuando se lanzó Toothpaste. Vi el beneficio de seguir lo que estaba sucediendo en el código fuente de React. Y una pregunta final, ¿cómo recomendarías que alguien comience con el modo concurrente? Comienza envolviendo las cosas con el modo estricto y observa cómo reacciona tu aplicación, luego continúa a partir de ahí.