Inbound Latino – Sitio web de Jose Sotelo | José Sotelo

Inbound Latino - Sitio web de Jose Sotelo | José Sotelo

Cómo evitar que las llamadas a tu API arruinen la fluidez de React Native: guía práctica de multihilo con Worklets

Si has desarrollado interfaces complejas en React Native, seguramente te has encontrado con una situación familiar: una animación funciona perfectamente durante las pruebas, pero cuando la aplicación comienza a procesar lógica real, actualizaciones de estado, renderizados y solicitudes de red, todo empieza a sentirse menos fluido. Los gestos responden con un ligero retraso y las transiciones pierden suavidad.

La razón es bastante sencilla. La lógica de React, las animaciones y el manejo de eventos comparten el mismo hilo principal de JavaScript. Al mismo tiempo, las animaciones necesitan renderizar cada cuadro dentro de una ventana de aproximadamente 16 milisegundos para mantener los 60 FPS. Cuando ese límite se supera, los usuarios perciben inmediatamente la pérdida de rendimiento.

Los Worklets solucionan este problema permitiendo que determinadas funciones sensibles al tiempo se ejecuten en un entorno JavaScript independiente que opera directamente sobre el hilo de interfaz. De esta manera, las animaciones dejan de competir con los procesos habituales de la aplicación y pueden mantenerse fluidas incluso cuando el hilo principal está ocupado.

Muchos desarrolladores asumen que existe un único entorno de ejecución JavaScript dentro de una aplicación React Native. Aunque esta idea es válida en la mayoría de los casos, Worklets introduce múltiples entornos que funcionan simultáneamente.

Para comprender mejor el concepto, conviene diferenciar dos términos. Un runtime es el entorno donde se ejecuta el código JavaScript, mientras que un thread es el contexto de ejecución proporcionado por el sistema operativo. Aunque suelen relacionarse, no representan exactamente lo mismo.

En una aplicación que utiliza Worklets encontramos dos categorías principales de runtimes: el runtime principal de React Native y los runtimes de Worklets.

El runtime principal se ejecuta en el hilo JavaScript tradicional. Allí residen los componentes de React, la gestión de estado, la navegación, las llamadas a servicios externos y prácticamente toda la lógica habitual de la aplicación. Este entorno tiene acceso completo a las APIs de React Native y existe una única instancia por aplicación.

Por otro lado, los runtimes de Worklets funcionan como contextos JavaScript independientes. No comparten memoria ni con el runtime principal ni entre ellos. Existen dos variantes principales.

La primera es el UI Runtime, que se ejecuta directamente en el hilo encargado de la interfaz gráfica. Su objetivo es gestionar tareas de alta prioridad, como cálculos de animaciones o respuestas inmediatas a gestos del usuario. Solo existe uno por aplicación.

La segunda variante corresponde a los Worker Runtimes. Estos se ejecutan en hilos de fondo y están diseñados para operaciones pesadas que no requieren interacción inmediata con la interfaz, como procesamiento de datos o cálculos complejos. Es posible tener varios funcionando simultáneamente.

Debido a que estos entornos están completamente aislados, los datos no pueden compartirse libremente. Cualquier información que deba cruzar de un runtime a otro necesita ser serializable. Por esta razón, los Worklets tienen restricciones sobre las variables y objetos a los que pueden acceder.

Para observar este comportamiento en la práctica, imaginemos una pantalla de prueba donde varios elementos visuales dependen de distintos runtimes. Si forzamos artificialmente una carga intensa sobre el hilo JavaScript principal mediante un bucle síncrono, podemos analizar cómo responde cada uno de ellos.

Una función que bloquea el hilo principal durante varios segundos impide que el bucle de eventos procese nuevas tareas. Mientras esta operación se ejecuta, no pueden gestionarse temporizadores, respuestas de red ni actualizaciones de estado.

El resultado es inmediato: los FPS asociados al hilo JavaScript caen drásticamente y cualquier elemento cuya posición o animación dependa de dicho hilo queda completamente congelado hasta que finaliza el bloqueo.

La situación cambia cuando la animación se ejecuta dentro del UI Runtime. Basta con convertir una función en Worklet para que su lógica se traslade al hilo de interfaz. Como las animaciones se calculan fuera del ciclo habitual de React, continúan ejecutándose con total suavidad incluso cuando el hilo JavaScript principal está saturado.

Sin embargo, hay escenarios donde ni el hilo de interfaz ni el hilo principal deberían asumir determinadas tareas. Procesamientos matemáticos intensivos o transformaciones complejas de datos pueden consumir demasiados recursos. Para estos casos entran en juego los Worker Runtimes.

Un Worker Runtime puede mantenerse activo en segundo plano y ejecutar cálculos de manera constante sin afectar la experiencia visual de la aplicación. Mientras realiza operaciones exigentes para la CPU, la interfaz permanece estable y las animaciones continúan funcionando con normalidad.

Cuando un Worker necesita comunicar resultados a la interfaz, se pueden utilizar mecanismos de sincronización compartida. Estos objetos especiales permiten que varios runtimes accedan al mismo valor de forma segura, algo que no sería posible con variables tradicionales.

El flujo habitual consiste en crear un valor sincronizable accesible desde distintos entornos. El Worker actualiza ese valor cuando termina una tarea, mientras que el UI Runtime supervisa los cambios y reacciona inmediatamente cuando detecta una actualización.

Este enfoque ofrece varias ventajas importantes. En primer lugar, la comunicación ocurre sin involucrar al hilo JavaScript principal. Además, los mecanismos internos garantizan la seguridad de acceso entre hilos diferentes. Finalmente, la interfaz puede responder prácticamente en tiempo real, activando animaciones o actualizaciones visuales en el siguiente cuadro disponible.

A pesar de sus beneficios, el trabajo multihilo introduce ciertas limitaciones que deben tenerse en cuenta. Los Worklets solo pueden acceder a datos serializables como números, cadenas de texto o valores booleanos. Objetos complejos, referencias de React y funciones convencionales no pueden utilizarse libremente.

También es importante recordar que los valores transferidos entre runtimes suelen comportarse como instantáneas del estado original. Cuando se necesitan actualizaciones continuas entre distintos entornos, es necesario recurrir a mecanismos específicos de sincronización.

Otro aspecto relevante es que los runtimes de Worklets no disponen de todas las APIs disponibles en el entorno principal. Elementos globales y determinados módulos comunes simplemente no existen dentro de estos contextos aislados.

Además, los Worker Runtimes funcionan como entornos cerrados dedicados exclusivamente a lógica y procesamiento. A diferencia del UI Runtime, no interactúan directamente con la jerarquía visual de la aplicación.

Por último, mover información entre hilos tiene un coste. Cada transferencia requiere serialización y coordinación interna. Si se envían pequeñas cantidades de datos con una frecuencia extremadamente alta, el gasto asociado a la comunicación puede terminar siendo mayor que la mejora de rendimiento obtenida. Por ello, resulta fundamental evaluar cuidadosamente qué operaciones merece la pena trasladar a otro runtime y cuáles deben permanecer en el hilo principal.