Inicio / Artículos

De Bridge a JSI: Cómo decidir cuándo migrar a la nueva arquitectura de React Native

Cómo JSI, Fabric y TurboModules reemplazan el antiguo puente de React Native, qué significan en la práctica las mejoras reportadas, y cómo auditar y migrar una aplicación existente de forma segura.

Editor: REACTAPP.TOP8 min de lectura1869 palabras

Fuentes: React Native’s New Architecture Finally Stopped Being a Compromise

Cualquiera que haya enviado una aplicación React Native compleja con la arquitectura antigua ha observado cómo el desplazamiento rápido se interrumpe en cuanto el hilo de JavaScript se ocupa de otra tarea, como analizar una respuesta de red o propagar un cambio de estado a través de un gran árbol de componentes. Esa interrupción no era mala suerte; era una consecuencia directa de cómo funcionaba el antiguo puente de comunicación. La Nueva Arquitectura, que lo reemplaza, ha sido la predeterminada desde React Native 0.76, pero durante mucho tiempo muchos equipos la consideraron algo que adoptar más adelante. Este artículo explica qué cambian realmente las tres partes de la Nueva Arquitectura, cómo interpretar las afirmaciones sobre su rendimiento y cómo decidir si y cómo migrar una aplicación existente.

Por qué el antiguo puente causaba interrupciones

En la arquitectura heredada, toda interacción entre JavaScript y código nativo pasaba por un puente asíncrono. Una llamada se serializaba a JSON, se ponía en cola en el puente, se deserializaba en el lado nativo y se ejecutaba allí, con los resultados que regresaban por el mismo camino. De este diseño se derivan dos consecuencias: cada paso requería un costo de serialización, y como el puente era asíncrono y ordenado, todo lo destinado al código nativo tenía que esperar cada vez que el hilo de JavaScript estaba ocupado. Por lo tanto, un hilo de JavaScript ocupado bloqueaba el desplazamiento, el diseño de la interfaz y los gestos que dependían de él.

Esa alternativa ha existido de alguna forma durante años y oficialmente se ha convertido en la predeterminada desde la versión 0.76. Sin embargo, ser la opción por defecto y ser algo en lo que un equipo cauteloso confiaría para una aplicación en producción son cosas distintas. Las versiones lanzadas en la primera mitad de este año, React Native 0.85 seguido por Expo SDK 56, son consideradas ampliamente como el momento en que esa brecha se cerró; los detalles se explican más adelante.

Las tres partes de la Nueva Arquitectura

Dejando de lado el marketing, la Nueva Arquitectura consta de tres componentes que trabajan juntos, cada uno reemplazando una parte específica del sistema de conexión.

JSI: llamadas directas en lugar de mensajes

La Interfaz de JavaScript (JSI) es la base. Se trata de una interfaz ligera en C++ a través de la cual JavaScript puede mantener referencias a objetos nativos y llamar directamente a funciones nativas, incluso de forma síncrona cuando eso es apropiado. No existe serialización en JSON, ni cola de puente, ni viajes asincrónicos para las llamadas que no lo requieren. El antiguo proceso de serialización, colocación en cola, deserialización y envío se reduce esencialmente a una llamada a función en C++.

Fabric: un árbol sombra compartido en C++

Fabric es el sistema de renderizado basado en JSI. Organiza el proceso de renderizado en tres capas: el árbol de fibra de React en JavaScript, un árbol de sombra y el árbol de vistas nativas del host. La diferencia clave es que ahora el árbol de sombra se encuentra en código C++ compartido, en lugar de ser gestionado a través del puente desde JavaScript. El diseño con Yoga se calcula en esa capa compartida, por lo que los datos de layout ya no deben pasar a través del puente.

Fabric también es lo que permite que las funcionalidades concurrentes de React, como las transiciones, Suspense y useTransition, funcionen en React Native. Su fase de confirmación puede ejecutarse de forma síncrona o concurrente, y el renderizador basado en puente no tenía la capacidad de soportar adecuadamente ninguno de los dos modos. Si su equipo desea el mismo modelo de renderizado concurrente que utiliza en la web, Fabric es un requisito previo y no una optimización.

TurboModules: módulos nativos perezosos y tipados

TurboModules reemplazan el sistema heredado NativeModules para acceder a la funcionalidad nativa. El fragmento muestra las diferencias entre ambos enfoques. La importación antigua obtiene un módulo de NativeModules, que se inicializaban de inmediato al arrancar y generaban costos de serialización en cada llamada. La importación nueva carga un módulo generado a partir de una especificación tipada, respaldado por JSI, que se inicializa solo la primera vez que se utiliza y puede llamarse de forma síncrona cuando sea apropiado.

// Old NativeModules: loaded eagerly at startup, whether used or not,
// with every call paying the bridge's serialization cost
import { NativeModules } from 'react-native';
const { CameraModule } = NativeModules;
// TurboModules: JSI-backed, lazily loaded only when actually called,
// with type-safe interfaces generated by Codegen from your spec
import CameraModule from './specs/NativeCameraModule';
CameraModule.capturePhoto(); // direct, synchronous-capable call via JSI

El cargado diferido es más importante de lo que parece a primera vista. En el sistema heredado, una aplicación con muchos módulos nativos pagaba un costo de inicialización al arrancar por cada uno, incluso si una sesión determinada nunca utilizaba la mayoría de ellos. TurboModules solo paga cuando un módulo se utiliza realmente. La especificación tipada representa la otra parte de esta ventaja: Codegen la convierte en interfaces nativas, lo cual se explica en detalle en nuestro artículo sobre por qué Codegen hace obligatoria la especificación primero para TurboModules.

Leer con atención las afirmaciones sobre el rendimiento

Las cifras citadas para la Nueva Arquitectura varían según la fuente y la carga de trabajo, por lo que deben considerarse como una orientación y no como valores concretos para prometer a las partes interesadas. Los resultados reportados incluyen:

  • un rendimiento del hilo de interfaz aproximadamente un 10 a un 30% mejor en las aplicaciones típicas, llegando hasta 3 veces mayor en aquellas que dependen en gran medida de módulos nativos
  • llamadas desde JavaScript hacia el código nativo un 20 a un 50% más rápidas
  • tiempos de inicio un 20 a un 40% más cortos, dependiendo de cuántos módulos nativos tenga una aplicación y de qué operaciones se midan

Un indicador más útil que cualquier porcentaje individual es saber qué categorías de aplicaciones pasaron de ser solucionables con workarounds a ser sencillas de implementar. Las aplicaciones que utilizan mucho la cámara, la visualización en tiempo real de datos, las interfaces animadas complejas y todo aquello que requiere mediciones sincrónicas reales del diseño necesitaban trucos arquitectónicos sofisticados o simplemente no eran prácticas. Con la Nueva Arquitectura, se convierten en tareas de ingeniería ordinarias. Si su aplicación pertenece a una de esas categorías, la motivación para migrar es fuerte independientemente de las cifras promedio; si se trata principalmente de formularios y listas, los beneficios serán reales pero más modestos. Las únicas cifras que deben influir en su decisión son las que usted mismo mida en su aplicación antes y después.

Lo que arregló el reciente ciclo de lanzamientos

La razón por la cual este año se considera un punto de inflexión no es una nueva funcionalidad, sino un conjunto de correcciones para los problemas específicos que hacían esperar a los equipos cautelosos. Consulte las notas oficiales de lanzamiento para obtener detalles exactos, ya que el resumen a continuación refleja los informes al momento de redactar este texto.

  • React Native 0.85, lanzado en abril, reconstruyó el sistema de animaciones compartido y eliminó una causa persistente de pérdida de fotogramas. Se informa que las transiciones con muchas animaciones mantienen 60 fps en la mayoría de los dispositivos compatibles, en lugar de deteriorarse bajo carga.
  • Esa misma versión corrigió un grupo importante de fugas de memoria tanto en Android como en iOS, lo que hacía que las sesiones prolongadas se volvieran progresivamente lentas. Este tipo de degradación lenta solo aparece después de que una aplicación esté abierta durante mucho tiempo, lo cual dificulta su detección en las pruebas de calidad habituales y su diagnóstico a partir de los informes de usuarios que indican que la aplicación comienza a comportarse de forma extraña gradualmente.
  • Expo SDK 56, lanzado en mayo, supuestamente reduce el tiempo de compilación local de aplicaciones de tamaño medio de 110 a 120 segundos a menos de 50, y disminuye el tiempo necesario para publicar actualizaciones sin conexión en aproximadamente un tercio. Para los equipos que lanzan aplicaciones a diario, estos ahorros se acumulan rápidamente.
  • La integración con React 19.2 aporta beneficios para los equipos que utilizan React Native Web o aplicaciones híbridas. Las actualizaciones en Suspense del lado del servidor y Web Streams permiten enviar una mayor parte de la interfaz de usuario con antelación, reduciendo el bloqueo del hilo principal, lo cual ayuda a la parte orientada a web del ecosistema que con frecuencia se subestima en las notas de lanzamiento.
  • Migración en la práctica: primero la auditoría de dependencias

    Nada de esto implica que todas las aplicaciones existentes deban cambiar mañana mismo. Lo primero a verificar antes de realizar cambios es la compatibilidad con las bibliotecas. En el momento de escribir esto, se informa que alrededor del 85% de los paquetes populares de React Native en npm son compatibles con la Nueva Arquitectura, lo que deja aproximadamente un 15% que solo son compatibles mediante puentes o de forma parcial. Las migraciones estancadas casi siempre se quedan en ese 15%, no en el propio framework, sino en algunos módulos nativos de terceros de los que depende la aplicación.

    Un patrón que se repite entre los equipos que migran con éxito es el siguiente:

    • Haga una auditoría antes de comenzar. Enumere todas las dependencias nativas y verifique si son compatibles con la nueva arquitectura según su versión actual, no basándose en lo que recuerde de hace uno o dos años. Para cada paquete no compatible, decida si actualizarlo, reemplazarlo, crear una copia modificada o manejarlo usted mismo.
    • Aproveche Codegen. Dado que Codegen genera interfaces nativas seguras desde sus especificaciones en TypeScript o JavaScript, toda clase de incompatibilidades entre JavaScript y código nativo se detectan en el momento de la compilación, en lugar de causar fallos en tiempo de ejecución. Si escribe sus propios módulos, nuestra guía sobre conectar un módulo Turbo dentro de la aplicación de extremo a extremo con Codegen muestra todo el proceso.
  • Migre de forma incremental. En lugar de realizar un cambio masivo de una sola vez, trabaje en una rama de características y mueva un módulo o pantalla a la vez, validando cada paso en lugar de arriesgar toda la aplicación en un único evento de migración.
  • Pruebe sesiones prolongadas. Dada la clase de errores que las versiones recientes han corregido, incluya pruebas de carga que mantengan la aplicación abierta y activa durante un período prolongado, y compare los niveles de memoria y las tasas de fotogramas antes y después.
  • Orientación según la situación

    Iniciar un proyecto nuevo

    Hay pocos motivos para evitarlo. La Nueva Arquitectura es la predeterminada; las herramientas asociadas, en particular el Expo SDK 56, asumen que se utiliza esta arquitectura, y la brecha de compatibilidad es ahora lo suficientemente pequeña como para que sea poco probable encontrar obstáculos, a menos que la aplicación tenga dependencias nativas inusuales.

    Mantener una aplicación en el puente heredado

    Comience con la auditoría de compatibilidad antes de tocar cualquier otra cosa, utilizando su lista real de dependencias. La proporción de paquetes compatibles ha ido en aumento constante, por lo que una evaluación realizada hace un año probablemente esté desactualizada. Planifique una migración gradual y considere los errores que presenta Codegen como una verdadera red de seguridad durante la transición, y no solo como un trámite formal.

    Equipos que lo evaluaron antes y decidieron esperar

    Posponerlo a 2023 o 2024 fue una decisión razonable dadas las deficiencias de la época. La reconstrucción de la animación y las correcciones de fugas de memoria en la versión 0.85 abordan precisamente las preocupaciones relacionadas con la estabilidad en producción que justificaron la espera, lo que convierte a este ciclo de lanzamientos en el momento adecuado para volver a plantear la cuestión.

    Puntos clave

    • El puente heredado serializaba cada llamada y la ponía en cola detrás de un hilo de JavaScript ocupado; eso, y no la mala suerte, fue la causa del mal rendimiento histórico.
    • JSI permite llamadas directas, Fabric traslada el árbol de sombras y el diseño a C++ compartido y habilita React concurrente, mientras que TurboModules se cargan de forma perezosa a partir de especificaciones tipadas.
    • Las mejoras en velocidad varían mucho; tome decisiones según la categoría de su aplicación y sus propias mediciones.
    • Lo que ha cambiado recientemente es la estabilidad y las herramientas, no las funcionalidades: la fiabilidad de las animaciones, el comportamiento de la memoria en sesiones largas y la velocidad de compilación.
    • El riesgo de migración radica en los módulos nativos de terceros, así que audite primero las dependencias, confíe en Codegen y mueva una pantalla o módulo a la vez.

    Lecturas relacionadas

  • Cómo React Query redujo 500 líneas de la capa API de una app React Native — Un estudio de caso real en React Native que muestra cómo pasar de la obtención manual de datos con useEffect a React Query eliminó el código repetitivo y mejoró el caché y el manejo fuera de línea.