Bridge — to architektoniczny komponent React Native, zapewniający asynchroniczną komunikację między wątkiem JavaScript a środowiskiem natywnym iOS i Android. Przesyła zserializowane wiadomości JSON przez kolejkę, umożliwiając wywoływanie natywnych API z kodu JS. Według Meta, 2024, Bridge pozostaje podstawą istniejących aplikacji, choć ustępuje wydajnością nowej architekturze opartej na JSI.
Najważniejsze
Bridge (Most) — to kluczowy element architektoniczny React Native, który zapewnia dwukierunkową asynchroniczną komunikację między wątkiem JavaScript, w którym wykonywana jest logika biznesowa aplikacji, a natywnymi wątkami iOS i Android. Od momentu wydania React Native w 2015 roku Bridge pozostawał jedynym sposobem interakcji kodu JS z platformowymi API — aparat, geolokalizacja, system plików, powiadomienia i inne natywne możliwości.
Architektura Bridge opiera się na zasadzie kolejki komunikatów (message queue). Gdy kod JavaScript wywołuje metodę natywną, żądanie jest serializowane do ciągu JSON, umieszczane w kolejce i asynchronicznie wysyłane na stronę natywną. Kod natywny przetwarza żądanie, wykonuje odpowiednią operację i wysyła wynik z powrotem przez tę samą kolejkę do wątku JS. Według raportu Meta na React Conf 2021, przez Bridge przechodzi do 10 000 wiadomości na sekundę w przeciętnej aplikacji.
Główne wątki uczestniczące w działaniu Bridge: JavaScript Thread (wykonanie kodu JS), Native Thread (wykonanie operacji natywnych) i Shadow Thread (obliczanie układu za pomocą Yoga). Każdy wątek działa niezależnie, co zapewnia responsywność interfejsu — natywne animacje nie są blokowane przez obliczenia JS.
Bridge wykorzystuje trzy kluczowe mechanizmy komunikacji: MessageQueue, serializację JSON i grupowanie wiadomości. MessageQueue to wewnętrzny komponent React Native, który zarządza kolejką wywołań między JS a stroną natywną. Każde wywołanie metody natywnej jest umieszczane w kolejce, serializowane i wysyłane w partiach w celu optymalizacji wydajności.
MessageQueue działa na zasadzie grupowania: wywołania metod natywnych są akumulowane i wysyłane jako jedna grupa (batch) co 5–15 milisekund. Zmniejsza to narzut na serializację, ponieważ kilka wywołań jest pakowanych w jeden pakiet JSON. Po stronie natywnej wiadomości są deserializowane i rozdzielane do odpowiednich modułów.
Moduły natywne są rejestrowane automatycznie przez makra lub adnotacje. W iOS używane jest makro RCT_EXPORT_MODULE, w Androidzie — adnotacja @ReactMethod. React Native skanuje zarejestrowane moduły podczas uruchamiania aplikacji i tworzy konfiguracyjną mapę JSON wszystkich dostępnych metod. Ta mapa jest przekazywana do środowiska JS, a JavaScript dowiaduje się, jakie metody można wywoływać.
Dane przechodzą następującą ścieżkę: JavaScript wywołuje NativeModules.CalendarModule.createCalendarEvent(). Metoda jest serializowana do wiadomości JSON z identyfikatorem modułu, nazwą metody i argumentami. Wiadomość trafia do kolejki MessageQueue. W wątku natywnym wiadomość jest deserializowana i przekazywana do odpowiedniego modułu. Wynik wykonania jest serializowany z powrotem i wysyłany do wątku JS jako Promise lub callback.
// Wywołanie modułu natywnego z JavaScript przez Bridge
import { NativeModules } from 'react-native';
const CalendarModule = NativeModules.CalendarModule;
CalendarModule.createCalendarEvent('Test Event', 'Office')
.then(eventId => {
console.log('Utworzono zdarzenie o id:', eventId);
})
.catch(error => {
console.error('Niepowodzenie:', error);
});
Po stronie natywnej iOS moduł wygląda jak klasa Objective-C z makrem RCT_EXPORT_MODULE. Metoda jest eksportowana za pomocą makra RCT_EXPORT_METHOD, a React Native automatycznie rejestruje ją w Bridge. Argumenty są przekazywane według pozycji i muszą odpowiadać obsługiwanym typom JSON: NSString, NSNumber, NSArray, NSDictionary, BOOL.
// Rejestracja natywnego modułu iOS w 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 ma szereg fundamentalnych ograniczeń wydajnościowych. Głównym z nich jest obowiązkowa asynchroniczność i serializacja. Każde wywołanie metody natywnej przekształca dane w ciąg JSON, co dodaje opóźnienie i zużywa pamięć. W przypadku operacji na dużych ilościach danych, takich jak przetwarzanie obrazów czy praca z wideo, staje się to wąskim gardłem.
Serializacja i deserializacja JSON zajmują czas procesora i pamięć. Każda wiadomość musi zostać przekształcona w ciąg po stronie JS, przesłana przez most i sparsowana po stronie natywnej. Według testów Callstack (2022), serializacja tablicy 10 000 liczb przez Bridge zajmuje około 30–50 milisekund, co jest nieakceptowalne dla wywołań o wysokiej częstotliwości.
Bridge nie jest zoptymalizowany do przesyłania dużych danych binarnych. Zdjęcia, pliki audio i strumienie wideo wymagają alternatywnych podejść — na przykład zapisu pliku na dysk i przesłania ścieżki jako ciągu znaków. Tworzy to dodatkowy narzut na odczyt i zapis systemu plików.
Świadomość tych ograniczeń doprowadziła zespół Meta do opracowania nowej architektury React Native, w której Bridge jest zastępowany przez JSI (JavaScript Interface) i Turbo Module. JSI umożliwia bezpośrednie wywoływanie metod natywnych bez serializacji, co eliminuje główną wadę Bridge.
Porównanie Bridge i Turbo Module pokazuje fundamentalne różnice w podejściach architektonicznych. Bridge używa asynchronicznej kolejki komunikatów z serializacją JSON, podczas gdy Turbo Module działa przez JSI — bezpośredni interfejs między JavaScript a C++, który umożliwia synchroniczne wywoływanie metod natywnych bez przekształcania danych.
| Cecha | Bridge | Turbo Module |
|---|---|---|
| Typ wywołania | Asynchroniczny | Synchroniczny i asynchroniczny |
| Serializacja | JSON przy każdym wywołaniu | Obiekty JSI bez kopiowania |
| Wydajność | Średnia | Wysoka |
| Typowanie | Dynamiczne | Statyczne (Codegen) |
| Ładowanie | Wszystkie moduły przy starcie | Leniwie (na żądanie) |
Wybór między Bridge a Turbo Module zależy od wersji React Native. Dla projektów na React Native 0.72 i starszych Bridge pozostaje głównym mechanizmem. Począwszy od React Native 0.73, Metro i nowa architektura są obsługiwane równolegle, umożliwiając stopniową migrację. Pełne przejście na Turbo Module wymaga aktualizacji do React Native 0.76+ i włączenia nowej architektury w konfiguracji.
Rozważmy pełny cykl tworzenia i używania Native Module przez Bridge na przykładzie modułu do pracy z kalendarzem. Moduł będzie tworzyć wydarzenie i zwracać jego identyfikator. Ten przykład obejmuje konfigurację dla obu platform — iOS i Android.
W Androidzie Native Module jest tworzony jako klasa Java dziedzicząca po ReactContextBaseJavaModule. Adnotacja @ReactMethod eksportuje metodę do Bridge. Dla Promise używany jest interfejs Promise z 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());
}
}
}
Moduł jest rejestrowany przez @ReactModule lub ręcznie w pakiecie aplikacji. React Native automatycznie wykrywa i dodaje go do Bridge. Po rejestracji moduł jest dostępny z JavaScript przez 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();
}
}
Ważne jest, że Bridge wymaga ponownego uruchomienia aplikacji przy dodawaniu nowych modułów, ponieważ mapa konfiguracyjna jest budowana raz podczas inicjalizacji. To odróżnia go od Turbo Module, który ładuje się leniwie i obsługuje hot-reload modułów bez restartu.
Często zadawane pytania
Bridge zawsze używa asynchronicznej kolejki i serializacji JSON, podczas gdy bezpośrednie przesyłanie przez JSI działa synchronicznie i bez kopiowania danych. Bridge tworzy opóźnienie na serializację, ale zapewnia izolację wątków.
Nie, Bridge obsługuje tylko wywołania asynchroniczne. Do synchronicznej interakcji wymagana jest nowa architektura z JSI i Turbo Module. To jedno z kluczowych ograniczeń, które zostało rozwiązane w React Native 0.76+.
Bridge obsługuje typy serializowalne do JSON: ciągi znaków, liczby, wartości logiczne, tablice, słowniki (obiekty). Dane binarne, takie jak obrazy, należy przesyłać przez system plików lub kodowanie base64.
Do pomiaru użyj React DevTools i profilera React Native. Zakładka Performance pokazuje liczbę wiadomości w kolejce Bridge i opóźnienia. Dostępny jest również pakiet react-native-bridge-spy do monitorowania ruchu.
Zaleca się przejście przy projektach wymagających wysokiej wydajności lub przy tworzeniu nowych aplikacji na React Native 0.76+. Dla istniejących projektów migracja może być stopniowa — obie architektury działają równolegle.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również