Как работает bridge между JavaScript и нативным кодом в React Native?

В React Native bridge (мост) — это ключевой архитектурный компонент, который обеспечивает взаимодействие между JavaScript-кодом и нативным кодом платформ Android и iOS. React Native позволяет писать бизнес-логику приложения на JavaScript, но поскольку для доступа к системным возможностям устройства (например, камеры, Bluetooth, датчиков, файловой системы) требуются нативные API, нужен механизм обмена данными между двумя средами исполнения: JavaScript-движком (JS Runtime) и нативными модулями (Java/Kotlin на Android, Objective-C/Swift на iOS). Эту роль и выполняет bridge.

Общая архитектура React Native с bridge

┌─────────────────────────────┐

│ JavaScript Thread │

│ (JS код, React, логика UI) │

└─────────────▲───────────────┘

│ Bridge (мост)

┌─────────────────────────────┐

│ Native Modules │

│ (Android/iOS API, UI Views) │

└─────────────────────────────┘

Bridge — это асинхронный и сериализованный канал связи между JavaScript и нативным кодом. Он позволяет:

  • JS-коду вызывать нативные функции

  • Нативному коду отправлять события обратно в JS

  • JS и native "разговаривают" через сериализацию/десериализацию данных (обычно в JSON)

Как работает bridge: шаг за шагом

  1. **JavaScript вызывает нативную функцию
    **

    • Через вызов функции, предоставленной в NativeModules

    • Например: NativeModules.MyModule.doSomething()

  2. **Аргументы сериализуются
    **

    • JS-движок (Hermes, JSC) сериализует аргументы (обычно в JSON или другой формат, поддерживаемый bridge)
  3. **Передача по bridge
    **

    • Сериализованные данные передаются через bridge к нативному коду

    • Используется асинхронный механизм, так как потоки JS и Native разные

  4. **Нативный код выполняет логику
    **

    • Например, обращается к камере, использует GPS или вызывает API Android/iOS
  5. **Ответ/событие возвращается в JS
    **

    • Если вызов был асинхронным (обычно так и есть), результат возвращается через callback, Promise, либо EventEmitter

    • Возвратные данные сериализуются и возвращаются по bridge обратно в JavaScript

Пример: вызов нативного метода из JavaScript

JS код:

import { NativeModules } from 'react-native';
NativeModules.MyModule.sayHello('Алексей');

Android (Java/Kotlin):

@ReactMethod

public void sayHello(String name) {
Log.d("MyModule", "Привет, " + name);
}

Метка @ReactMethod сообщает React Native, что метод доступен для вызова из JS через bridge.

Асинхронность

Bridge работает асинхронно по нескольким причинам:

  • Потоки JS и native разные, и нельзя блокировать один другим

  • Большинство нативных операций (например, доступ к файлам или геолокации) сами по себе асинхронны

  • Используются callback или Promise для возвращения результатов

NativeModules.MyModule.getDeviceName().then(name => {
console.log('Имя устройства:', name);
});

Отправка событий из native в JS

Нативный код может инициировать события, которые JavaScript слушает.

Java (Android):

private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}

JS:

import { NativeEventEmitter, NativeModules } from 'react-native';
const eventEmitter = new NativeEventEmitter(NativeModules.MyModule);
eventEmitter.addListener('MyEvent', (data) => {
console.log('Получено событие:', data);
});

Пример: создание собственного модуля (Android)

  1. **Создание Java-класса
    **
public class MyModule extends ReactContextBaseJavaModule {
MyModule(ReactApplicationContext context) {
super(context);
}
@NonNull
@Override
public String getName() {
return "MyModule";
}
@ReactMethod
public void sayHello(String name) {
Log.d("MyModule", "Привет, " + name);
}
}
  1. **Регистрация модуля
    **
public class MyPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext context) {
return Arrays.asList(new MyModule(context));
}
// если нужно создать View
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext context) {
return Collections.emptyList();
}
}

Особенности bridge:

Свойство Значение
Асинхронный Да
--- ---
Сериализованный Да
--- ---
Основан на JSON-подобных данных Да
--- ---
Потоки JS и native Разные
--- ---
Двунаправленный Да
--- ---

Проблемы старого bridge (до 2022)

  1. Производительность: каждый вызов требует сериализации и проходит по одному каналу

  2. Асинхронные границы: сложно гарантировать последовательность вызовов

  3. Ограничения типов данных: нельзя передавать сложные типы (например, функции, классы)

  4. Нет shared memory: JavaScript и native не могут напрямую делиться памятью

Новый архитектурный подход: JSI (JavaScript Interface)

Появился в рамках новой архитектуры React Native (Fabric + TurboModules):

  • JSI заменяет bridge

  • Позволяет вызывать native-функции синхронно

  • Нет необходимости в сериализации в JSON

  • Более высокая производительность и меньше задержек

  • Нативные модули реализуются как TurboModule

  • UI теперь строится через Fabric Renderer, а не через bridge

Но JSI пока внедряется поэтапно, и множество библиотек продолжают использовать старый bridge.

Основные способы взаимодействия через bridge

Сценарий Метод
JS → Native NativeModules.MyModule.fn()
--- ---
Native → JS (инициирует событие) DeviceEventEmitter.emit()
--- ---
Возврат результата (одноразовый) Promise или callback
--- ---
Подписка на изменения NativeEventEmitter
--- ---

Bridge — это один из краеугольных камней старой архитектуры React Native. Он обеспечивает связь между миром JavaScript и нативными API, позволяя JS-коду вызывать платформенные функции и слушать события от native-модулей. Несмотря на то, что bridge имеет ограничения, он всё ещё широко используется и поддерживается, особенно в существующих проектах и сторонних библиотеках. Переход на новую архитектуру с JSI и TurboModules происходит постепенно, и bridge остаётся актуальным инструментом для расширения возможностей React Native-приложений.