Arquitectura nativa de React Native: Bridge, JSI, TurboModules y Fabric
React Native permite construir interfaces móviles usando JavaScript o TypeScript, pero una aplicación React Native no funciona únicamente dentro del mundo JavaScript.
En una aplicación real existen varias capas: JavaScript, el runtime de JavaScript, el código nativo de iOS o Android y la interfaz gráfica nativa. Comprender estas capas es fundamental para entender el rendimiento, la comunicación entre JavaScript y código nativo, y la evolución de React Native.
Visión general de la arquitectura
De forma simplificada podemos imaginar la arquitectura así:
JavaScript / React
|
v
Runtime de JavaScript
|
v
Capa de interoperabilidad
|
v
Código nativo
/ \
v v
iOS UIKit Android Views
\ /
\ /
Plataforma
JavaScript controla gran parte de la lógica de la aplicación, mientras que el código nativo permite acceder a capacidades específicas de la plataforma, como la cámara, el GPS, Bluetooth, notificaciones, archivos, sensores, APIs específicas de iOS o Android y componentes visuales nativos.
¿Qué significa "nativo"?
En React Native existen dos mundos principales.
El mundo JavaScript contiene React, JavaScript o TypeScript, el estado, la lógica de negocio y los componentes React.
El mundo nativo contiene Swift y Objective-C en iOS, Kotlin y Java en Android, las APIs de cada sistema operativo, y las views y componentes nativos.
React Native proporciona mecanismos para conectar ambos mundos.
El Bridge
Históricamente, React Native utilizó el llamado Bridge como mecanismo principal de comunicación entre JavaScript y código nativo.
Conceptualmente:
JavaScript
|
| mensajes
v
Bridge
|
v
Código nativo
Por ejemplo, JavaScript podría solicitar una operación nativa:
JS -> "ejecuta esta operación"
Bridge -> transporta la información
Native -> ejecuta la operación
Native -> devuelve un resultado
Bridge -> transporta el resultado
JS -> recibe el resultado
El Bridge fue una pieza fundamental de la arquitectura original de React Native.
Sin embargo, la comunicación mediante mensajes tiene costes asociados. Si existe una gran cantidad de comunicación entre JavaScript y nativo, pueden aparecer serialización y deserialización de datos, coste de transferencia de mensajes, trabajo adicional entre las capas y dificultades para realizar determinadas operaciones de forma síncrona.
Por estas razones, React Native evolucionó hacia una arquitectura más directa.
JSI: JavaScript Interface
JSI significa JavaScript Interface. Es una interfaz de bajo nivel que permite que código C++ interactúe directamente con el runtime de JavaScript.
Conceptualmente:
JavaScript Runtime
|
v
JSI
|
v
C++
|
v
Código nativo
JSI no es simplemente "un Bridge más rápido". La diferencia conceptual importante es que JSI permite exponer objetos y funciones nativas al runtime de JavaScript mediante una interfaz de bajo nivel, reduciendo la dependencia de un sistema basado exclusivamente en mensajes serializados.
Esto permite nuevas posibilidades arquitectónicas y sirve como una de las bases de la New Architecture de React Native.
TurboModules
TurboModules son el sistema moderno de React Native para crear y acceder a módulos nativos.
Un módulo nativo representa funcionalidad que JavaScript puede utilizar, pero cuya implementación pertenece al código nativo. Por ejemplo:
JavaScript
|
v
CameraModule
|
v
Android / iOS API
Un módulo podría proporcionar una función conceptual como esta:
const result = await NativeDevice.getBatteryLevel();
La implementación real de getBatteryLevel() estaría en código nativo.
TurboModules utilizan la infraestructura moderna de React Native y se apoyan en mecanismos como JSI y Codegen.
Codegen
Codegen significa generación de código. En la arquitectura moderna, una interfaz puede definirse mediante una especificación que describe qué métodos, propiedades o eventos existen.
Conceptualmente:
Especificación
|
v
Codegen
|
v
Código generado
|
v
Implementación nativa
Esto permite que JavaScript y el código nativo compartan un contrato más explícito. Por ejemplo, una especificación podría definir:
getBatteryLevel(): Promise<number>
La implementación nativa debe respetar ese contrato. Esto reduce ambigüedades entre las capas.
Fabric
Fabric es el nuevo renderer de React Native. Su responsabilidad principal está relacionada con cómo React Native transforma el árbol de componentes React en una representación que puede materializarse en la interfaz nativa.
Conceptualmente:
React Components
|
v
React Shadow Tree
|
v
Fabric
|
v
Native UI
Fabric reemplaza la arquitectura anterior de renderizado y está diseñado para trabajar con las capacidades modernas de React Native. Entre sus objetivos están una integración más estrecha con React, mejor coordinación del renderizado y una arquitectura más eficiente y flexible.
Shadow Tree
Para comprender Fabric conviene conocer el concepto de Shadow Tree. El árbol de React no es directamente la jerarquía visual nativa:
View
|
+-- Text
|
+-- Button
React Native mantiene estructuras intermedias que representan el estado y la configuración de los componentes:
React Tree
|
v
Shadow Tree
|
v
Native Views
El Shadow Tree representa la información necesaria para determinar cómo debe construirse y actualizarse la interfaz.
Native Components
Un Native Component es un componente visual cuya implementación final utiliza una vista nativa de la plataforma.
Ejemplo conceptual:
<NativeMap />
JavaScript puede utilizar un componente como <MapView /> mientras que internamente puede existir una implementación nativa equivalente a una UIView o vista especializada en iOS, o una View o clase especializada en Android.
La diferencia fundamental es que React Native proporciona una interfaz declarativa para controlar ese componente desde JavaScript.
Native Modules vs Native Components
Es importante no confundir ambos conceptos.
Un Native Module proporciona funcionalidad:
NativeBattery.getLevel()
Un Native Component representa una interfaz visual:
<NativeMap />
Podemos resumirlo así:
Native Module
-> lógica / APIs / funcionalidades
Native Component
-> interfaz visual / views
Un módulo puede incluso controlar funcionalidades relacionadas con un componente nativo, pero conceptualmente cumplen funciones diferentes.
Comunicación entre JavaScript y nativo
La comunicación puede representarse así:
JavaScript
|
| llamada
v
Native Module
|
v
Plataforma
Para una operación asíncrona:
JS
|
| await
v
Native
|
| resultado
v
Promise
|
v
JS
Por ejemplo:
const location = await NativeLocation.getCurrentPosition();
JavaScript realiza la llamada y recibe posteriormente el resultado. En otros escenarios pueden existir eventos que van del nativo hacia un listener de JavaScript, como batteryChanged, locationChanged o bluetoothStateChanged:
Native
|
| evento
v
JS listener
Props y eventos en componentes nativos
Los Native Components necesitan comunicación en ambas direcciones. JavaScript puede enviar propiedades:
<NativeCamera
zoom={2}
flashEnabled={true}
/>
El componente nativo recibe esos valores. Pero el componente también puede producir eventos:
<NativeCamera onCapture={handleCapture} />
El flujo conceptual es:
JS
|
| props
v
Native Component
|
| event
v
JS
Esto permite construir componentes declarativos manteniendo la implementación visual en código nativo.
Old Architecture vs New Architecture
La arquitectura anterior se apoyaba principalmente en esta estructura:
JavaScript
|
Bridge
|
Native
La arquitectura moderna incorpora una infraestructura compartida en C++ que conecta JavaScript con TurboModules y Fabric:
JavaScript
|
JSI
|
C++ / infraestructura
/ \
/ \
v v
TurboModules Fabric
| |
v v
Native APIs Native UI
La comparación no significa que todos los componentes internos funcionen literalmente como este diagrama simplificado. Es una representación conceptual de la evolución arquitectónica.
Por qué existe C++ en la arquitectura
React Native necesita compartir infraestructura entre iOS y Android. C++ permite construir partes comunes de la arquitectura que no dependen directamente de una sola plataforma:
React Native Core
|
C++
/ \
v v
iOS Android
Esto ayuda a compartir mecanismos internos entre plataformas.
No significa que toda una aplicación React Native deba escribirse en C++. Normalmente el desarrollador trabaja principalmente con JavaScript o TypeScript, y utiliza Swift, Objective-C, Kotlin o Java cuando necesita integración nativa específica.
Ejemplo conceptual de un módulo nativo
JavaScript podría tener algo como esto:
import { NativeModules } from "react-native";
async function getBattery() {
const level = await NativeModules.Battery.getLevel();
console.log(level);
}
La idea arquitectónica es:
JavaScript
|
v
Battery
|
v
Native implementation
|
v
Operating System API
En una implementación moderna basada en TurboModules, la arquitectura utiliza interfaces tipadas, Codegen y los mecanismos de la New Architecture.
Cuándo necesitas código nativo
No todo debe convertirse en Native Module. React Native ya proporciona APIs para muchas operaciones comunes.
El código nativo suele ser necesario cuando React Native no expone una API requerida, cuando existe una biblioteca nativa que necesitas integrar, cuando se necesita acceso a una API específica de iOS o Android, cuando se necesita un componente visual nativo especializado, cuando existe una operación cuyo rendimiento requiere integración de bajo nivel, o cuando se necesitan integrar SDKs externos.
Las capas tienen responsabilidades
Una arquitectura saludable mantiene responsabilidades claras.
JavaScript se ocupa del estado, la lógica de aplicación, los componentes React y el flujo de interacción.
TurboModules se ocupan de las APIs nativas, la funcionalidad no visual y la comunicación tipada con código nativo.
Fabric se ocupa del renderizado moderno, la integración con componentes nativos y la actualización de la interfaz.
JSI ofrece la interfaz de bajo nivel con el runtime de JavaScript y la infraestructura para comunicación directa con código nativo.
El código nativo se ocupa de las APIs específicas de la plataforma, las implementaciones de iOS y Android y los componentes nativos especializados.
Resumen mental
Una forma sencilla de recordar la arquitectura moderna es este esquema:
React
|
v
JavaScript
|
v
Runtime
|
v
JSI
|
+-------------------+
| |
v v
TurboModules Fabric
| |
v v
Native APIs Native UI
| |
+---------+---------+
|
v
iOS / Android
Las piezas cumplen funciones diferentes:
JSI
-> interfaz de bajo nivel entre JavaScript y el runtime.
TurboModules
-> acceso moderno a funcionalidad nativa.
Fabric
-> renderer moderno para la interfaz.
Native Modules
-> funcionalidad implementada en código nativo.
Native Components
-> componentes visuales implementados con vistas nativas.
Codegen
-> generación de código a partir de contratos tipados.
Conclusión
La arquitectura nativa de React Native consiste en conectar dos mundos: el ecosistema React y JavaScript, y las capacidades nativas de iOS y Android.
El Bridge fue fundamental en la arquitectura original. La New Architecture introduce mecanismos como JSI, TurboModules, Fabric y Codegen para construir una comunicación y un sistema de renderizado más modernos.
La idea esencial no es memorizar nombres, sino comprender responsabilidades. JSI es la interfaz de bajo nivel, TurboModules es la funcionalidad nativa, Fabric es el renderizado, los Native Modules son las APIs nativas, los Native Components son la UI nativa, y Codegen genera código a partir de contratos tipados.
Cuando estas piezas se entienden como capas con responsabilidades definidas, resulta mucho más sencillo analizar rendimiento, crear integraciones nativas y comprender qué ocurre detrás de un componente aparentemente simple de React Native.
© Arturo Juárez Monroy