Native Module este o clasă Java sau Objective-C care face API-urile native ale platformei accesibile din JavaScript în React Native. Fiecare modul se înregistrează în Bridge și exportă metode care pot fi apelate din codul JS ca funcții obișnuite. Potrivit Meta, 2024, Native Module rămâne principala modalitate de integrare a codului platformei în aplicațiile React Native.
Principalele puncte
Native Module este un element arhitectural React Native care permite executarea codului în limbajul platformei (Objective-C/Swift pentru iOS, Java/Kotlin pentru Android) și returnarea rezultatului în JavaScript. Fără Native Module este imposibil accesul la capacitățile native ale dispozitivului — cameră, GPS, accelerometru, sistem de fișiere sau Bluetooth.
React Native vine cu un set de module Native Module încorporate: CameraRoll, AsyncStorage, Geolocation, NetInfo și altele. Însă pentru sarcini specifice — integrarea unui SDK terț, lucrul cu senzori hardware sau procese de fundal — dezvoltatorul își creează propriile module. Conform sondajului State of React Native 2024, 67% dintre dezvoltatori folosesc cel puțin un Native Module personalizat în proiectele lor.
Arhitectura Native Module depinde de versiunea React Native. În arhitectura clasică (React Native 0.72 și mai vechi) modulul se conectează prin Bridge și comunică cu JS asincron prin serializare JSON. În arhitectura nouă (React Native 0.76+) modulul poate funcționa ca Turbo Module, folosind JSI pentru acces sincron fără serializare.
Crearea Native Module pentru iOS începe cu declararea unei clase Objective-C care implementează protocolul RCTBridgeModule. Macro-ul RCT_EXPORT_MODULE înregistrează modulul în Bridge, iar RCT_EXPORT_METHOD exportă o metodă accesibilă din JavaScript.
// ImageCompressor.m — Native Module pentru 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 primește calea către imagine și calitatea compresiei (0.0–1.0), procesează datele pe partea nativă și returnează calea către fișierul comprimat. Avantajul cheie — compresia se execută cu cod nativ, ceea ce este semnificativ mai rapid și mai eficient din punct de vedere al memoriei decât o operație similară în JavaScript.
Pentru modulele Swift se folosește adnotarea @objc înaintea clasei și metodelor pentru a fi accesibile runtime-ului Objective-C cu care lucrează Bridge. Clasa trebuie să moștenească NSObject și să implementeze RCTBridgeModule.
// ImageCompressor.swift — Native Module Swift
@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)
}
}
Pe Android Native Module se creează ca o clasă Java care moștenește ReactContextBaseJavaModule. Adnotarea @ReactMethod exportă metoda în Bridge. Pentru returnarea rezultatului se folosește interfața Promise din 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());
}
}
}
Metoda getName() returnează numele modulului sub care va fi accesibil din JavaScript. În exemplul dat modulul se înregistrează ca ImageCompressor. Adnotarea @ReactMethod indică Bridge-ului că metoda trebuie exportată. Important: metodele trebuie să fie void și să accepte doar tipuri suportate de Bridge: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
După crearea clasei, modulul trebuie înregistrat în pachetul aplicației. Pentru aceasta se creează o clasă care implementează ReactPackage și se adaugă la lista de module în metoda createNativeModules.
// ImageCompressorPackage.java — înregistrare modul
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();
}
}
După crearea modulelor pentru ambele platforme, acestea trebuie înregistrate în React Native. Pentru Android pachetul se adaugă în MainApplication.java în metoda getPackages(). Pentru iOS modulul se înregistrează automat prin macro-ul RCT_EXPORT_MODULE, dar se poate folosi și înregistrarea manuală în fișierul AppDelegate.mm.
// MainApplication.java — adăugarea pachetului la 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;
}
};
}
După înregistrare, modulul devine accesibil în JavaScript prin NativeModules. React Native înlocuiește automat numele modulului specificat în getName() pentru Android sau în RCT_EXPORT_MODULE pentru iOS.
// Utilizarea Native Module din 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('Comprimat:', result);
return result;
} catch (error) {
console.error('Comprimare eșuată:', error);
throw error;
}
}
Comparația Native Module clasic și Turbo Module ajută să înțelegeți ce abordare să alegeți pentru un proiect nou. Ambele mecanisme oferă acces la codul nativ, dar diferă fundamental prin arhitectură și performanță.
| Caracteristică | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Comunicare | Asincronă prin JSON | Sincronă prin JSI |
| Serializare | JSON la fiecare apel | Fără copierea datelor |
| Tipizare | Manuală, fără generare | Automată prin Codegen |
| Încărcare | La inițializarea aplicației | Întârziată (lazy load) |
| Compatibilitate | Toate versiunile React Native | React Native 0.73+ |
Pentru proiectele existente pe React Native 0.72 și mai vechi, Native Module clasic rămâne alegerea principală. Pentru proiecte noi se recomandă utilizarea Turbo Module, mai ales dacă este necesară performanță ridicată la apeluri frecvente ale metodelor native. Odată cu actualizarea treptată a React Native, comunitatea se îndreaptă către tranziția completă la noua arhitectură.
Întrebări frecvente
Da, Bridge suportă callback. În loc de Promise se pot folosi funcțiile de apel invers RCTResponseSenderBlock în iOS și Callback în Android. Totuși, Promise este considerat standardul modern și este recomandat pentru modulele noi.
Native Module se depanează ca orice cod nativ — setați puncte de întrerupere în Xcode sau Android Studio. Pentru iOS folosiți schema de build cu React Native, pentru Android — configurația Debug. Punctul de intrare — metodele apelate din JS.
Native Module prin Bridge nu suportă date binare (NSData/byte[]), obiecte personalizate și funcții. Pentru transmiterea imaginilor folosiți calea către fișier sau șirul base64. Turbo Module prin JSI elimină o parte din aceste limitări.
De obicei nu — Native Module API este stabil și compatibil retroactiv. La trecerea la noua arhitectură (Turbo Module) modulul se adaptează prin decoratori, dar codul existent continuă să funcționeze.
Folosiți RCTEventEmitter în iOS sau DeviceEventEmitter în Android. Modulul trimite evenimentul, iar partea JS se abonează prin NativeEventEmitter din react-native. Acest lucru este util pentru date în flux și evenimente ale senzorilor.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și