Как работает 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: шаг за шагом
-
**JavaScript вызывает нативную функцию
**-
Через вызов функции, предоставленной в NativeModules
-
Например: NativeModules.MyModule.doSomething()
-
-
**Аргументы сериализуются
**- JS-движок (Hermes, JSC) сериализует аргументы (обычно в JSON или другой формат, поддерживаемый bridge)
-
**Передача по bridge
**-
Сериализованные данные передаются через bridge к нативному коду
-
Используется асинхронный механизм, так как потоки JS и Native разные
-
-
**Нативный код выполняет логику
**- Например, обращается к камере, использует GPS или вызывает API Android/iOS
-
**Ответ/событие возвращается в 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)
- **Создание 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);
}
}
- **Регистрация модуля
**
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)
-
Производительность: каждый вызов требует сериализации и проходит по одному каналу
-
Асинхронные границы: сложно гарантировать последовательность вызовов
-
Ограничения типов данных: нельзя передавать сложные типы (например, функции, классы)
-
Нет 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-приложений.