Native Module — это Java или Objective-C класс, который делает нативные API платформы доступными из JavaScript в React Native. Каждый модуль регистрируется в Bridge и экспортирует методы, которые можно вызывать из JS-кода как обычные функции. По данным Meta, 2024, Native Module остаётся основным способом интеграции платформенного кода в React Native-приложения.
Главное
Native Module — это архитектурный элемент React Native, который позволяет выполнять код на языке платформы (Objective-C/Swift для iOS, Java/Kotlin для Android) и возвращать результат в JavaScript. Без Native Module невозможно получить доступ к нативным возможностям устройства — камере, GPS, акселерометру, файловой системе или Bluetooth.
React Native поставляется с набором встроенных Native Module: CameraRoll, AsyncStorage, Geolocation, NetInfo и другие. Однако для специфических задач — интеграция стороннего SDK, работа с аппаратными датчиками или фоновые процессы — разработчик создаёт собственные модули. По данным опроса State of React Native 2024, 67% разработчиков используют как минимум один кастомный Native Module в своих проектах.
Архитектура Native Module зависит от версии React Native. В классической архитектуре (React Native 0.72 и старше) модуль подключается через Bridge и общается с JS асинхронно через JSON-сериализацию. В новой архитектуре (React Native 0.76+) модуль может работать как Turbo Module, используя JSI для синхронного доступа без сериализации.
Создание Native Module для iOS начинается с объявления Objective-C класса, который реализует протокол RCTBridgeModule. Макрос RCT_EXPORT_MODULE регистрирует модуль в Bridge, а RCT_EXPORT_METHOD экспортирует метод, доступный из JavaScript.
// ImageCompressor.m — Native Module for iOS
@interface ImageCompressor () RCT_EXPORT_MODULE()
@end
@implementation ImageCompressor
RCT_EXPORT_METHOD(compressImage:(NSString *)imagePath
quality:(NSNumber *)quality
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
NSData *compressedData = [UIImageJPEGRepresentation(image, quality.floatValue)];
NSString *outputPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"compressed.jpg"];
[compressedData writeToFile:outputPath atomically:YES];
resolve(outputPath);
}
@end
Метод compressImage принимает путь к изображению и качество сжатия (0.0–1.0), обрабатывает данные на нативной стороне и возвращает путь к сжатому файлу. Ключевое преимущество — сжатие выполняется нативным кодом, что значительно быстрее и эффективнее по памяти, чем аналогичная операция на JavaScript.
Для Swift-модулей используется аннотация @objc перед классом и методами, чтобы они были доступны Objective-C рантайму, с которым работает Bridge. При этом класс должен наследовать NSObject и реализовывать RCTBridgeModule.
// ImageCompressor.swift — Swift Native Module
@objc(ImageCompressor)
class ImageCompressor: NSObject {
@objc
func compressImage(
_ imagePath: String,
quality: Float,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock
) {
guard let image = UIImage(contentsOfFile: imagePath) else {
rejecter("FILE_ERROR", "Cannot load image", nil)
return
}
guard let data = image.jpegData(compressionQuality: CGFloat(quality)) else {
rejecter("COMPRESS_ERROR", "Compression failed", nil)
return
}
let outputPath = NSTemporaryDirectory() + "compressed.jpg"
try? data.write(to: URL(fileURLWithPath: outputPath))
resolver(outputPath)
}
}
На Android Native Module создаётся как Java-класс, наследующий ReactContextBaseJavaModule. Аннотация @ReactMethod экспортирует метод в Bridge. Для возврата результата используется интерфейс Promise из com.facebook.react.bridge.
// ImageCompressorModule.java — Android Native Module
public class ImageCompressorModule
extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "ImageCompressor";
}
@ReactMethod
public void compressImage(
String imagePath,
Float quality,
Promise promise) {
try {
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
File outputFile = new File(
ReactNative.getApplicationContext()
.getCacheDir(), "compressed.jpg");
FileOutputStream fos = new FileOutputStream(outputFile);
bitmap.compress(
Bitmap.CompressFormat.JPEG,
(int)(quality * 100), fos);
fos.close();
promise.resolve(outputFile.getAbsolutePath());
} catch (Exception e) {
promise.reject("COMPRESS_ERROR", e.getMessage());
}
}
}
Метод getName() возвращает имя модуля, под которым он будет доступен из JavaScript. В приведённом примере модуль регистрируется как ImageCompressor. Аннотация @ReactMethod указывает Bridge, что метод должен быть экспортирован. Важно: методы должны быть void и принимать только типы, поддерживаемые Bridge: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
После создания класса модуль необходимо зарегистрировать в пакете приложения. Для этого создаётся класс, реализующий ReactPackage, и добавляется в список модулей в методе createNativeModules.
// ImageCompressorPackage.java — module registration
public class ImageCompressorPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.asList(
new ImageCompressorModule(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
После создания модулей для обеих платформ их необходимо зарегистрировать в React Native. Для Android пакет добавляется в MainApplication.java в метод getPackages(). Для iOS модуль автоматически регистрируется через макрос RCT_EXPORT_MODULE, но можно использовать и ручную регистрацию в файле AppDelegate.mm.
// MainApplication.java — adding package to React Native
import com.yourapp.nativemodules.ImageCompressorPackage;
public class MainApplication extends Application
implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages =
new PackageList(this).getPackages();
packages.add(new ImageCompressorPackage());
return packages;
}
};
}
После регистрации модуль становится доступным в JavaScript через NativeModules. React Native автоматически подставляет имя модуля, указанное в getName() для Android или в RCT_EXPORT_MODULE для iOS.
// Using Native Module from JavaScript
import { NativeModules } from 'react-native';
import { Platform } from 'react-native';
const ImageCompressor = NativeModules.ImageCompressor;
async function compressPhoto(uri: string) {
try {
const result = await ImageCompressor.compressImage(
uri.replace('file://', ''), 0.8
);
console.log('Compressed:', result);
return result;
} catch (error) {
console.error('Compression failed:', error);
throw error;
}
}
Сравнение классического Native Module и Turbo Module помогает понять, какой подход выбрать для нового проекта. Оба механизма предоставляют доступ к нативному коду, но принципиально различаются по архитектуре и производительности.
| Характеристика | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Коммуникация | Асинхронная через JSON | Синхронная через JSI |
| Сериализация | JSON на каждый вызов | Без копирования данных |
| Типизация | Ручная, без генерации | Авто через Codegen |
| Загрузка | При инициализации приложения | Ленивая (lazy load) |
| Совместимость | Все версии React Native | React Native 0.73+ |
Для существующих проектов на React Native 0.72 и старше классические Native Module остаются основным выбором. Для новых проектов рекомендуется использовать Turbo Module, особенно если требуется высокая производительность при частых вызовах нативных методов. С постепенным обновлением React Native сообщество движется к полному переходу на новую архитектуру.
Часто задаваемые вопросы
Да, Bridge поддерживает callback. Вместо Promise можно использовать функции обратного вызова RCTResponseSenderBlock в iOS и Callback в Android. Однако Promise считается современным стандартом и рекомендуется для новых модулей.
Native Module отлаживается как обычный нативный код — установите брейкпоинты в Xcode или Android Studio. Для iOS используйте схему сборки с React Native, для Android — Debug-конфигурацию. Точка входа — методы, вызванные из JS.
Native Module через Bridge не поддерживает бинарные данные (NSData/byte[]), пользовательские объекты и функции. Для передачи изображений используйте путь к файлу или base64-строку. Turbo Module через JSI снимает часть этих ограничений.
Обычно нет — Native Module API стабилен и обратно совместим. При переходе на новую архитектуру (Turbo Module) модуль адаптируется через декораторы, но существующий код продолжает работать.
Используйте RCTEventEmitter в iOS или DeviceEventEmitter в Android. Модуль отправляет событие, а JS-сторона подписывается через NativeEventEmitter из react-native. Это полезно для потоковых данных и событий датчиков.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также