Bridge es un componente arquitectónico de React Native que proporciona comunicación asíncrona entre el hilo de JavaScript y el entorno nativo de iOS y Android. Transmite mensajes JSON serializados a través de una cola, permitiendo llamar a APIs nativas desde código JS. Según Meta, 2024, Bridge sigue siendo la base de las aplicaciones existentes, aunque pierde en rendimiento frente a la nueva arquitectura basada en JSI.
Puntos clave
Bridge es un elemento arquitectónico clave de React Native que proporciona comunicación asíncrona bidireccional entre el hilo de JavaScript, donde se ejecuta la lógica de negocio de la aplicación, y los hilos nativos de iOS y Android. Desde el lanzamiento de React Native en 2015, Bridge ha sido la única forma en que el código JS interactúa con las APIs de la plataforma (cámara, geolocalización, sistema de archivos, notificaciones y otras capacidades nativas).
La arquitectura de Bridge se basa en el principio de cola de mensajes (message queue). Cuando el código JavaScript llama a un método nativo, la solicitud se serializa en una cadena JSON, se coloca en una cola y se envía asíncronamente al lado nativo. El código nativo procesa la solicitud, realiza la operación correspondiente y envía el resultado de vuelta a través de la misma cola al hilo JS. Según el informe de Meta en React Conf 2021, hasta 10 000 mensajes por segundo pasan a través de Bridge en una aplicación promedio.
Los principales hilos que participan en el funcionamiento de Bridge: JavaScript Thread (ejecución de código JS), Native Thread (ejecución de operaciones nativas) y Shadow Thread (cálculo del diseño con Yoga). Cada hilo trabaja de forma independiente, lo que garantiza la capacidad de respuesta de la IU: las animaciones nativas no se bloquean por los cálculos de JS.
Bridge utiliza tres mecanismos clave para la comunicación: MessageQueue, serialización JSON y agrupación de mensajes (batching). MessageQueue es un componente interno de React Native que gestiona la cola de llamadas entre JS y el lado nativo. Cada llamada a un método nativo se coloca en una cola, se serializa y se envía en lotes para optimizar el rendimiento.
MessageQueue funciona con el principio de agrupación: las llamadas a métodos nativos se acumulan y se envían como un solo grupo (lote) cada 5–15 milisegundos. Esto reduce la sobrecarga de serialización, ya que varias llamadas se empaquetan en un solo paquete JSON. En el lado nativo, los mensajes se deserializan y se distribuyen a los módulos correspondientes.
Los módulos nativos se registran automáticamente a través de macros o anotaciones. iOS utiliza la macro RCT_EXPORT_MODULE, Android utiliza la anotación @ReactMethod. React Native escanea los módulos registrados al iniciar la aplicación y construye un mapa JSON de configuración con todos los métodos disponibles. Este mapa se pasa al entorno JS y JavaScript aprende qué métodos se pueden llamar.
Los datos siguen este camino: JavaScript llama a NativeModules.CalendarModule.createCalendarEvent(). El método se serializa en un mensaje JSON con el identificador del módulo, el nombre del método y los argumentos. El mensaje entra en MessageQueue. En el hilo nativo, el mensaje se deserializa y se pasa al módulo correspondiente. El resultado de la ejecución se serializa de vuelta y se envía al hilo JS como una Promise o callback.
// Native module call from JavaScript via Bridge
import { NativeModules } from 'react-native';
const CalendarModule = NativeModules.CalendarModule;
CalendarModule.createCalendarEvent('Test Event', 'Office')
.then(eventId => {
console.log('Created event with id:', eventId);
})
.catch(error => {
console.error('Failed:', error);
});
En el lado nativo de iOS, el módulo se ve como una clase Objective-C con la macro RCT_EXPORT_MODULE. El método se exporta con la macro RCT_EXPORT_METHOD, y React Native lo registra automáticamente en Bridge. Los argumentos se pasan por posición y deben corresponder a los tipos JSON compatibles: NSString, NSNumber, NSArray, NSDictionary, BOOL.
// iOS Native Module registration in Bridge
@interface CalendarModule () RCT_EXPORT_MODULE()
@end
@implementation CalendarModule
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name
location:(NSString *)location
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSNumber *eventId = createEvent(name, location);
resolve(eventId);
}
@end
Bridge tiene una serie de limitaciones fundamentales de rendimiento. La principal es la asincronía y serialización obligatorias. Cada llamada a un método nativo convierte los datos en una cadena JSON, lo que añade latencia y consume memoria. Para operaciones con grandes volúmenes de datos, como el procesamiento de imágenes o el trabajo con video, esto se convierte en un cuello de botella.
La serialización y deserialización JSON consumen tiempo de CPU y memoria. Cada mensaje debe convertirse en una cadena en el lado JS, transmitirse a través del puente y analizarse en el lado nativo. Según las pruebas de Callstack (2022), serializar un array de 10 000 números a través de Bridge toma entre 30 y 50 milisegundos, lo que es inaceptable para llamadas de alta frecuencia.
Bridge no está optimizado para transferir grandes datos binarios. Las fotos, los archivos de audio y los flujos de video requieren enfoques alternativos, como escribir un archivo en disco y pasar la ruta como una cadena. Esto crea una sobrecarga adicional en las operaciones de lectura y escritura del sistema de archivos.
El reconocimiento de estas limitaciones llevó al equipo de Meta a desarrollar una nueva arquitectura de React Native, donde Bridge es reemplazado por JSI (JavaScript Interface) y Turbo Module. JSI permite llamar a métodos nativos directamente, sin serialización, eliminando la principal desventaja de Bridge.
La comparación de Bridge y Turbo Module muestra diferencias fundamentales en los enfoques arquitectónicos. Bridge utiliza una cola de mensajes asíncrona con serialización JSON, mientras que Turbo Module funciona a través de JSI, una interfaz directa entre JavaScript y C++ que permite llamar a métodos nativos de forma síncrona sin conversión de datos.
| Característica | Bridge | Turbo Module |
|---|---|---|
| Tipo de llamada | Asíncrona | Síncrona y asíncrona |
| Serialización | JSON en cada llamada | Objetos JSI sin copia |
| Rendimiento | Medio | Alto |
| Tipificación | Dinámica | Estática (Codegen) |
| Carga | Todos los módulos al inicio | Perezosa (bajo demanda) |
La elección entre Bridge y Turbo Module depende de la versión de React Native. Para proyectos en React Native 0.72 y anteriores, Bridge sigue siendo el mecanismo principal. A partir de React Native 0.73, Metro y la nueva arquitectura son compatibles en paralelo, lo que permite una migración gradual. La transición completa a Turbo Module requiere actualizar a React Native 0.76+ y habilitar la nueva arquitectura en la configuración.
Veamos el ciclo completo de creación y uso de un Native Module a través de Bridge usando como ejemplo un módulo para trabajar con el calendario. El módulo creará un evento y devolverá su identificador. Este ejemplo cubre la configuración para ambas plataformas: iOS y Android.
En Android, un Native Module se crea como una clase Java que extiende ReactContextBaseJavaModule. La anotación @ReactMethod exporta el método a Bridge. Para Promise, se utiliza la interfaz Promise de com.facebook.react.bridge.
public class CalendarModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "CalendarModule";
}
@ReactMethod
public void createCalendarEvent(
String name,
String location,
Promise promise) {
try {
Integer eventId = createCalendarEventNative(name, location);
promise.resolve(eventId);
} catch (Exception e) {
promise.reject("EVENT_ERROR", e.getMessage());
}
}
}
El módulo se registra a través de @ReactModule o manualmente en el paquete de la aplicación. React Native lo detecta automáticamente y lo agrega a Bridge. Después del registro, el módulo está disponible desde JavaScript a través de NativeModules.
public class CalendarPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new CalendarModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Es importante señalar que Bridge requiere reiniciar la aplicación al agregar nuevos módulos, ya que el mapa de configuración se construye una vez durante la inicialización. Esto lo diferencia de Turbo Module, que se carga de forma perezosa y admite la recarga en caliente de módulos sin reinicio.
Preguntas frecuentes
Bridge siempre usa una cola asíncrona y serialización JSON, mientras que la transferencia directa a través de JSI funciona de forma síncrona y sin copia de datos. Bridge crea latencia de serialización, pero garantiza el aislamiento de hilos.
No, Bridge solo admite llamadas asíncronas. La interacción síncrona requiere la nueva arquitectura con JSI y Turbo Module. Esta es una de las limitaciones clave que se ha resuelto en React Native 0.76+.
Bridge admite tipos serializables a JSON: cadenas, números, valores booleanos, arrays, diccionarios (objetos). Los datos binarios, como imágenes, deben transferirse a través del sistema de archivos o codificación base64.
Para medirlo, usa React DevTools y el perfilador de React Native. La pestaña Performance muestra la cantidad de mensajes en la cola de Bridge y las latencias. También está disponible el paquete react-native-bridge-spy para monitorear el tráfico.
Se recomienda migrar en proyectos exigentes en rendimiento o al crear nuevas aplicaciones en React Native 0.76+. Para proyectos existentes, la migración puede ser gradual: ambas arquitecturas funcionan en paralelo.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también