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 для 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 — Native Module для Android
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 — реєстрація модуля
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 — додавання пакета до 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.
// Використання Native Module з 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('Стиснено:', result);
return result;
} catch (error) {
console.error('Помилка стиснення:', 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також