Bridge è un componente architetturale di React Native che fornisce comunicazione asincrona tra il thread JavaScript e l’ambiente nativo di iOS e Android. Trasmette messaggi JSON serializzati attraverso una coda, consentendo di chiamare API native dal codice JS. Secondo Meta, 2024, Bridge rimane la base delle applicazioni esistenti, sebbene sia inferiore in termini di prestazioni rispetto alla nuova architettura basata su JSI.
Punti chiave
Bridge è un elemento architetturale chiave di React Native che fornisce comunicazione asincrona bidirezionale tra il thread JavaScript, dove viene eseguita la logica di business dell’applicazione, e i thread nativi iOS e Android. Dal rilascio di React Native nel 2015, Bridge è stato l’unico modo per il codice JS di interagire con le API della piattaforma — fotocamera, geolocalizzazione, file system, notifiche e altre funzionalità native.
L’architettura Bridge si basa sul principio della coda di messaggi (message queue). Quando il codice JavaScript chiama un metodo nativo, la richiesta viene serializzata in una stringa JSON, inserita in una coda e inviata in modo asincrono al lato nativo. Il codice nativo elabora la richiesta, esegue l’operazione corrispondente e invia il risultato attraverso la stessa coda al thread JS. Secondo il rapporto di Meta alla React Conf 2021, fino a 10.000 messaggi al secondo attraversano Bridge in un’applicazione media.
I principali thread coinvolti nel funzionamento di Bridge: JavaScript Thread (esecuzione del codice JS), Native Thread (esecuzione di operazioni native) e Shadow Thread (calcolo del layout con Yoga). Ogni thread lavora in modo indipendente, garantendo la reattività dell’interfaccia utente — le animazioni native non vengono bloccate dai calcoli JS.
Bridge utilizza tre meccanismi chiave per la comunicazione: MessageQueue, serializzazione JSON e raggruppamento dei messaggi (batching). MessageQueue è un componente interno di React Native che gestisce la coda delle chiamate tra JS e il lato nativo. Ogni chiamata di metodo nativo viene inserita in una coda, serializzata e inviata in lotti per ottimizzare le prestazioni.
MessageQueue opera sul principio del raggruppamento: le chiamate di metodo nativo si accumulano e vengono inviate come un unico gruppo (lotto) ogni 5–15 millisecondi. Ciò riduce l’overhead di serializzazione, poiché più chiamate vengono impacchettate in un unico pacchetto JSON. Sul lato nativo, i messaggi vengono deserializzati e distribuiti ai moduli corrispondenti.
I moduli nativi vengono registrati automaticamente tramite macro o annotazioni. iOS utilizza la macro RCT_EXPORT_MODULE, Android utilizza l’annotazione @ReactMethod. React Native scansiona i moduli registrati all’avvio dell’applicazione e costruisce una mappa JSON di configurazione di tutti i metodi disponibili. Questa mappa viene passata all’ambiente JS e JavaScript scopre quali metodi possono essere chiamati.
I dati seguono questo percorso: JavaScript chiama NativeModules.CalendarModule.createCalendarEvent(). Il metodo viene serializzato in un messaggio JSON con l’identificatore del modulo, il nome del metodo e gli argomenti. Il messaggio entra in MessageQueue. Sul thread nativo, il messaggio viene deserializzato e passato al modulo corrispondente. Il risultato dell’esecuzione viene serializzato di nuovo e inviato al thread JS come 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);
});
Sul lato nativo iOS, il modulo si presenta come una classe Objective-C con la macro RCT_EXPORT_MODULE. Il metodo viene esportato con la macro RCT_EXPORT_METHOD e React Native lo registra automaticamente in Bridge. Gli argomenti vengono passati per posizione e devono corrispondere ai tipi JSON supportati: 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 presenta una serie di limitazioni fondamentali delle prestazioni. La principale è l’asincronicità e la serializzazione obbligatorie. Ogni chiamata di metodo nativo converte i dati in una stringa JSON, aggiungendo latenza e consumando memoria. Per operazioni con grandi volumi di dati, come l’elaborazione di immagini o il lavoro con video, questo diventa un collo di bottiglia.
La serializzazione e deserializzazione JSON consumano tempo CPU e memoria. Ogni messaggio deve essere convertito in una stringa sul lato JS, trasmesso attraverso il ponte e analizzato sul lato nativo. Secondo i test di Callstack (2022), serializzare un array di 10.000 numeri attraverso Bridge richiede circa 30–50 millisecondi, il che è inaccettabile per chiamate ad alta frequenza.
Bridge non è ottimizzato per il trasferimento di grandi dati binari. Foto, file audio e streaming video richiedono approcci alternativi — ad esempio, scrivere un file su disco e passare il percorso come stringa. Ciò crea overhead aggiuntivo sulle operazioni di lettura e scrittura del file system.
La consapevolezza di queste limitazioni ha portato il team di Meta a sviluppare una nuova architettura React Native, in cui Bridge viene sostituito da JSI (JavaScript Interface) e Turbo Module. JSI consente di chiamare i metodi nativi direttamente, senza serializzazione, eliminando il principale svantaggio di Bridge.
Il confronto tra Bridge e Turbo Module mostra differenze fondamentali negli approcci architetturali. Bridge utilizza una coda di messaggi asincrona con serializzazione JSON, mentre Turbo Module funziona attraverso JSI — un’interfaccia diretta tra JavaScript e C++ che consente di chiamare metodi nativi in modo sincrono senza conversione dei dati.
| Caratteristica | Bridge | Turbo Module |
|---|---|---|
| Tipo di chiamata | Asincrona | Sincrona e asincrona |
| Serializzazione | JSON ad ogni chiamata | Oggetti JSI senza copia |
| Prestazioni | Medie | Alte |
| Tipizzazione | Dinamica | Static (Codegen) |
| Caricamento | Tutti i moduli all’avvio | Lazy (su richiesta) |
La scelta tra Bridge e Turbo Module dipende dalla versione di React Native. Per i progetti su React Native 0.72 e precedenti, Bridge rimane il meccanismo principale. A partire da React Native 0.73, Metro e la nuova architettura sono supportati in parallelo, consentendo una migrazione graduale. Una transizione completa a Turbo Module richiede l’aggiornamento a React Native 0.76+ e l’attivazione della nuova architettura nella configurazione.
Esaminiamo il ciclo completo di creazione e utilizzo di un Native Module tramite Bridge usando come esempio un modulo calendario. Il modulo creerà un evento e restituirà il suo identificatore. Questo esempio copre la configurazione per entrambe le piattaforme — iOS e Android.
Su Android, un Native Module viene creato come una classe Java che estende ReactContextBaseJavaModule. L’annotazione @ReactMethod esporta il metodo in Bridge. Per Promise, viene utilizzata l’interfaccia Promise di 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());
}
}
}
Il modulo viene registrato tramite @ReactModule o manualmente nel pacchetto dell’applicazione. React Native lo rileva automaticamente e lo aggiunge a Bridge. Dopo la registrazione, il modulo è accessibile da JavaScript tramite 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();
}
}
È importante notare che Bridge richiede il riavvio dell’applicazione quando si aggiungono nuovi moduli, poiché la mappa di configurazione viene costruita una volta durante l’inizializzazione. Questo lo distingue da Turbo Module, che viene caricato in modo lazy e supporta il ricaricamento a caldo dei moduli senza riavvio.
Domande frequenti
Bridge utilizza sempre una coda asincrona e serializzazione JSON, mentre il trasferimento diretto tramite JSI funziona in modo sincrono e senza copia dei dati. Bridge crea latenza di serializzazione ma garantisce l’isolamento dei thread.
No, Bridge supporta solo chiamate asincrone. L’interazione sincrona richiede la nuova architettura con JSI e Turbo Module. Questa è una delle limitazioni principali che è stata risolta in React Native 0.76+.
Bridge supporta i tipi serializzabili in JSON: stringhe, numeri, valori booleani, array, dizionari (oggetti). I dati binari come le immagini devono essere trasferiti tramite il file system o la codifica base64.
Per la misurazione, utilizzare React DevTools e il profiler React Native. La scheda Performance mostra il numero di messaggi nella coda di Bridge e le latenze. Il pacchetto react-native-bridge-spy è anche disponibile per il monitoraggio del traffico.
Si consiglia di passare per progetti che richiedono prestazioni elevate o quando si creano nuove applicazioni su React Native 0.76+. Per i progetti esistenti, la migrazione può essere graduale — entrambe le architetture funzionano in parallelo.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche