Το Native Module είναι μια κλάση Java ή Objective-C που καθιστά τα εγγενή API της πλατφόρμας προσβάσιμα από το JavaScript στο React Native. Κάθε module καταχωρίζεται στο 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 τρίτου μέρους, εργασία με αισθητήρες υλικού ή διεργασίες παρασκηνίου — ο προγραμματιστής δημιουργεί τα δικά του modules. Σύμφωνα με την έρευνα State of React Native 2024, το 67% των προγραμματιστών χρησιμοποιεί τουλάχιστον ένα προσαρμοσμένο Native Module στα έργα τους.
Η αρχιτεκτονική του Native Module εξαρτάται από την έκδοση του React Native. Στην κλασική αρχιτεκτονική (React Native 0.72 και παλαιότερες) το module συνδέεται μέσω Bridge και επικοινωνεί με το JS ασύγχρονα μέσω σειριοποίησης JSON. Στη νέα αρχιτεκτονική (React Native 0.76+) το module μπορεί να λειτουργήσει ως Turbo Module, χρησιμοποιώντας JSI για σύγχρονη πρόσβαση χωρίς σειριοποίηση.
Η δημιουργία Native Module για iOS ξεκινά με τη δήλωση μιας κλάσης Objective-C που υλοποιεί το πρωτόκολλο RCTBridgeModule. Η μακροεντολή RCT_EXPORT_MODULE καταχωρίζει το 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.
Για modules 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() επιστρέφει το όνομα του module υπό το οποίο θα είναι προσβάσιμο από το JavaScript. Στο δεδομένο παράδειγμα, το module καταχωρίζεται ως ImageCompressor. Ο σχολιασμός @ReactMethod υποδεικνύει στο Bridge ότι η μέθοδος πρέπει να εξαχθεί. Σημαντικό: οι μέθοδοι πρέπει να είναι void και να δέχονται μόνο τύπους που υποστηρίζονται από το Bridge: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Μετά τη δημιουργία της κλάσης, το module πρέπει να καταχωριστεί στο πακέτο εφαρμογής. Για αυτό δημιουργείται μια κλάση που υλοποιεί το ReactPackage και προστίθεται στη λίστα modules στη μέθοδο createNativeModules.
// ImageCompressorPackage.java — καταχώριση module
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();
}
}
Μετά τη δημιουργία modules και για τις δύο πλατφόρμες, πρέπει να καταχωριστούν στο React Native. Για Android, το πακέτο προστίθεται στο MainApplication.java στη μέθοδο getPackages(). Για iOS, το module καταχωρίζεται αυτόματα μέσω της μακροεντολής 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;
}
};
}
Μετά την καταχώριση, το module γίνεται προσβάσιμο στο JavaScript μέσω NativeModules. Το React Native αντικαθιστά αυτόματα το όνομα module που καθορίστηκε στο 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 θεωρείται σύγχρονο πρότυπο και συνιστάται για νέα modules.
Το Native Module γίνεται debug όπως ο συνηθισμένος εγγενής κώδικας — ορίστε σημεία διακοπής στο Xcode ή στο Android Studio. Για iOS χρησιμοποιήστε το σχήμα build με React Native, για Android — διαμόρφωση Debug. Το σημείο εισόδου — μέθοδοι που καλούνται από το JS.
Native Module μέσω Bridge δεν υποστηρίζει δυαδικά δεδομένα (NSData/byte[]), προσαρμοσμένα αντικείμενα και συναρτήσεις. Για μετάδοση εικόνων χρησιμοποιήστε διαδρομή αρχείου ή συμβολοσειρά base64. Το Turbo Module μέσω JSI αφαιρεί μέρος αυτών των περιορισμών.
Συνήθως όχι — το Native Module API είναι σταθερό και συμβατό προς τα πίσω. Κατά τη μετάβαση στη νέα αρχιτεκτονική (Turbo Module) το module προσαρμόζεται μέσω διακοσμητών, αλλά ο υπάρχων κώδικας συνεχίζει να λειτουργεί.
Χρησιμοποιήστε το RCTEventEmitter στο iOS ή το DeviceEventEmitter στο Android. Το module στέλνει το συμβάν και η πλευρά JS εγγράφεται μέσω NativeEventEmitter από το react-native. Αυτό είναι χρήσιμο για δεδομένα ροής και συμβάντα αισθητήρων.
Περίληψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης