Native Module to klasa Java lub Objective-C, która udostępnia natywne API platformy z poziomu JavaScript w React Native. Każdy moduł rejestruje się w Bridge i eksportuje metody, które można wywoływać z kodu JS jak zwykłe funkcje. Według danych Meta, 2024, Native Module pozostaje głównym sposobem integracji kodu platformowego w aplikacjach React Native.
Najważniejsze
Native Module to element architektoniczny React Native, który pozwala wykonywać kod w języku platformy (Objective-C/Swift dla iOS, Java/Kotlin dla Android) i zwracać wynik do JavaScript. Bez Native Module niemożliwy jest dostęp do natywnych możliwości urządzenia — aparatu, GPS, akcelerometru, systemu plików czy Bluetooth.
React Native jest dostarczany z zestawem wbudowanych Native Module: CameraRoll, AsyncStorage, Geolocation, NetInfo i inne. Jednak do specyficznych zadań — integracja zewnętrznego SDK, praca z czujnikami sprzętowymi czy procesy w tle — programista tworzy własne moduły. Według ankiety State of React Native 2024, 67% programistów używa co najmniej jednego niestandardowego Native Module w swoich projektach.
Architektura Native Module zależy od wersji React Native. W klasycznej architekturze (React Native 0.72 i starsze) moduł podłącza się przez Bridge i komunikuje z JS asynchronicznie przez serializację JSON. W nowej architekturze (React Native 0.76+) moduł może działać jako Turbo Module, używając JSI do synchronicznego dostępu bez serializacji.
Tworzenie Native Module dla iOS zaczyna się od zadeklarowania klasy Objective-C, która implementuje protokół RCTBridgeModule. Makro RCT_EXPORT_MODULE rejestruje moduł w Bridge, a RCT_EXPORT_METHOD eksportuje metodę dostępną z JavaScript.
// ImageCompressor.m — Native Module dla 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
Metoda compressImage przyjmuje ścieżkę do obrazu i jakość kompresji (0.0–1.0), przetwarza dane po stronie natywnej i zwraca ścieżkę do skompresowanego pliku. Kluczowa zaleta — kompresja wykonuje się natywnym kodem, co jest znacznie szybsze i bardziej wydajne pamięciowo niż analogiczna operacja w JavaScript.
Dla modułów Swift używa się adnotacji @objc przed klasą i metodami, aby były dostępne dla środowiska uruchomieniowego Objective-C, z którym pracuje Bridge. Klasa musi dziedziczyć po NSObject i implementować 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)
}
}
Na Android Native Module tworzy się jako klasę Java dziedziczącą po ReactContextBaseJavaModule. Adnotacja @ReactMethod eksportuje metodę do Bridge. Do zwracania wyniku używa się interfejsu Promise z 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());
}
}
}
Metoda getName() zwraca nazwę modułu, pod którą będzie dostępny z JavaScript. W podanym przykładzie moduł rejestruje się jako ImageCompressor. Adnotacja @ReactMethod wskazuje Bridge, że metoda powinna być wyeksportowana. Ważne: metody muszą być void i przyjmować tylko typy obsługiwane przez Bridge: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Po utworzeniu klasy moduł należy zarejestrować w pakiecie aplikacji. W tym celu tworzy się klasę implementującą ReactPackage i dodaje do listy modułów w metodzie createNativeModules.
// ImageCompressorPackage.java — rejestracja modułu
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();
}
}
Po utworzeniu modułów dla obu platform należy je zarejestrować w React Native. Dla Android pakiet dodaje się w MainApplication.java w metodzie getPackages(). Dla iOS moduł rejestruje się automatycznie przez makro RCT_EXPORT_MODULE, ale można też użyć ręcznej rejestracji w pliku AppDelegate.mm.
// MainApplication.java — dodawanie pakietu do 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;
}
};
}
Po rejestracji moduł staje się dostępny w JavaScript przez NativeModules. React Native automatycznie podstawia nazwę modułu podaną w getName() dla Android lub w RCT_EXPORT_MODULE dla iOS.
// Używanie Native Module z 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('Skompresowano:', result);
return result;
} catch (error) {
console.error('Kompresja nieudana:', error);
throw error;
}
}
Porównanie klasycznego Native Module i Turbo Module pomaga zrozumieć, które podejście wybrać dla nowego projektu. Oba mechanizmy zapewniają dostęp do kodu natywnego, ale zasadniczo różnią się architekturą i wydajnością.
| Cecha | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Komunikacja | Asynchroniczna przez JSON | Synchroniczna przez JSI |
| Serializacja | JSON przy każdym wywołaniu | Bez kopiowania danych |
| Typowanie | Ręczne, bez generacji | Automatyczne przez Codegen |
| Ładowanie | Przy inicjalizacji aplikacji | Leniwę (lazy load) |
| Kompatybilność | Wszystkie wersje React Native | React Native 0.73+ |
Dla istniejących projektów na React Native 0.72 i starszych klasyczne Native Module pozostają głównym wyborem. Dla nowych projektów zaleca się używanie Turbo Module, szczególnie jeśli wymagana jest wysoka wydajność przy częstych wywołaniach metod natywnych. Wraz ze stopniową aktualizacją React Native społeczność zmierza w kierunku pełnego przejścia na nową architekturę.
Często zadawane pytania
Tak, Bridge obsługuje callback. Zamiast Promise można używać funkcji zwrotnych RCTResponseSenderBlock w iOS i Callback w Android. Jednak Promise uważa się za nowoczesny standard i jest zalecany dla nowych modułów.
Native Module debuguje się jak zwykły kod natywny — ustaw punkty przerwania w Xcode lub Android Studio. Dla iOS użyj schematu budowania z React Native, dla Android — konfiguracji Debug. Punkt wejścia — metody wywołane z JS.
Native Module przez Bridge nie obsługuje danych binarnych (NSData/byte[]), obiektów niestandardowych i funkcji. Do przesyłania obrazów użyj ścieżki do pliku lub ciągu base64. Turbo Module przez JSI usuwa część tych ograniczeń.
Zazwyczaj nie — Native Module API jest stabilny i wstecznie zgodny. Przy przejściu na nową architekturę (Turbo Module) moduł adaptuje się przez dekoratory, ale istniejący kod nadal działa.
Użyj RCTEventEmitter w iOS lub DeviceEventEmitter w Android. Moduł wysyła zdarzenie, a strona JS subskrybuje przez NativeEventEmitter z react-native. Jest to przydatne do danych strumieniowych i zdarzeń czujników.
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ż