Native Module is een Java of Objective-C klasse die native API’s van het platform toegankelijk maakt vanuit JavaScript in React Native. Elke module wordt geregistreerd in de Bridge en exporteert methoden die vanuit JS-code als gewone functies kunnen worden aangeroepen. Volgens Meta, 2024 blijft Native Module de belangrijkste manier om platformcode te integreren in React Native-applicaties.
Belangrijkste punten
Native Module is een architecturale component van React Native waarmee code in de platformtaal (Objective-C/Swift voor iOS, Java/Kotlin voor Android) kan worden uitgevoerd en het resultaat aan JavaScript kan worden teruggegeven. Zonder Native Module is toegang tot native apparaatmogelijkheden — camera, GPS, accelerometer, bestandssysteem of Bluetooth — onmogelijk.
React Native wordt geleverd met een set ingebouwde Native Modules: CameraRoll, AsyncStorage, Geolocation, NetInfo en andere. Voor specifieke taken — integratie van externe SDK’s, werken met hardwaresensoren of achtergrondprocessen — maakt de ontwikkelaar echter eigen modules. Volgens de State of React Native 2024-enquête gebruikt 67% van de ontwikkelaars ten minste één aangepaste Native Module in hun projecten.
De architectuur van Native Module hangt af van de React Native-versie. In de klassieke architectuur (React Native 0.72 en ouder) wordt de module via Bridge verbonden en communiceert asynchroon met JS via JSON-serialisatie. In de nieuwe architectuur (React Native 0.76+) kan de module als Turbo Module werken, met behulp van JSI voor synchrone toegang zonder serialisatie.
Het aanmaken van een Native Module voor iOS begint met het declareren van een Objective-C-klasse die het RCTBridgeModule-protocol implementeert. De macro RCT_EXPORT_MODULE registreert de module in de Bridge en RCT_EXPORT_METHOD exporteert een methode die toegankelijk is vanuit JavaScript.
// ImageCompressor.m — Native Module voor 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
De methode compressImage ontvangt het pad naar de afbeelding en de compressiekwaliteit (0.0–1.0), verwerkt de gegevens aan de native kant en retourneert het pad naar het gecomprimeerde bestand. Het belangrijkste voordeel — compressie wordt uitgevoerd met native code, wat aanzienlijk sneller en geheugenefficiënter is dan een vergelijkbare bewerking in JavaScript.
Voor Swift-modules wordt de annotatie @objc voor de klasse en methoden gebruikt, zodat ze toegankelijk zijn voor de Objective-C runtime waar Bridge mee werkt. De klasse moet overerven van NSObject en RCTBridgeModule implementeren.
// 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)
}
}
Op Android wordt Native Module gemaakt als een Java-klasse die overerft van ReactContextBaseJavaModule. De annotatie @ReactMethod exporteert de methode naar de Bridge. Voor het retourneren van het resultaat wordt de Promise-interface uit com.facebook.react.bridge gebruikt.
// 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());
}
}
}
De methode getName() retourneert de naam van de module waaronder deze toegankelijk is vanuit JavaScript. In het gegeven voorbeeld wordt de module geregistreerd als ImageCompressor. De annotatie @ReactMethod geeft de Bridge aan dat de methode moet worden geëxporteerd. Belangrijk: methoden moeten void zijn en alleen typen accepteren die door Bridge worden ondersteund: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Na het maken van de klasse moet de module worden geregistreerd in het applicatiepakket. Hiervoor wordt een klasse gemaakt die ReactPackage implementeert en toegevoegd aan de lijst met modules in de methode createNativeModules.
// ImageCompressorPackage.java — moduleregistratie
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();
}
}
Na het aanmaken van de modules voor beide platforms moeten ze worden geregistreerd in React Native. Voor Android wordt het pakket toegevoegd in MainApplication.java in de methode getPackages(). Voor iOS wordt de module automatisch geregistreerd via de macro RCT_EXPORT_MODULE, maar handmatige registratie in het bestand AppDelegate.mm is ook mogelijk.
// MainApplication.java — pakket toevoegen aan 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;
}
};
}
Na registratie wordt de module toegankelijk in JavaScript via NativeModules. React Native vervangt automatisch de modulenaam die is opgegeven in getName() voor Android of in RCT_EXPORT_MODULE voor iOS.
// Native Module gebruiken vanuit 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('Gecomprimeerd:', result);
return result;
} catch (error) {
console.error('Compressie mislukt:', error);
throw error;
}
}
Een vergelijking van klassieke Native Module en Turbo Module helpt te begrijpen welke aanpak voor een nieuw project te kiezen. Beide mechanismen bieden toegang tot native code, maar verschillen fundamenteel in architectuur en prestaties.
| Kenmerk | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Communicatie | Asynchroon via JSON | Synchroon via JSI |
| Serialisatie | JSON bij elke aanroep | Zonder gegevenskopiering |
| Typering | Handmatig, zonder generatie | Automatisch via Codegen |
| Laden | Bij initialisatie van de app | Lazy load |
| Compatibiliteit | Alle React Native-versies | React Native 0.73+ |
Voor bestaande projecten op React Native 0.72 en ouder blijft klassieke Native Module de belangrijkste keuze. Voor nieuwe projecten wordt het gebruik van Turbo Module aanbevolen, vooral als hoge prestaties vereist zijn bij frequente aanroepen van native methoden. Met de geleidelijke update van React Native beweegt de gemeenschap richting een volledige overgang naar de nieuwe architectuur.
Veelgestelde vragen
Ja, Bridge ondersteunt callbacks. In plaats van Promise kunnen de callback-functies RCTResponseSenderBlock in iOS en Callback in Android worden gebruikt. Promise wordt echter beschouwd als de moderne standaard en wordt aanbevolen voor nieuwe modules.
Native Module wordt gedebugd als gewone native code — plaats breekpunten in Xcode of Android Studio. Gebruik voor iOS het buildschema met React Native, voor Android — Debug-configuratie. Het toegangspunt — methoden die vanuit JS worden aangeroepen.
Native Module ondersteunt via Bridge geen binaire gegevens (NSData/byte[]), aangepaste objecten en functies. Gebruik voor het verzenden van afbeeldingen het bestandspad of een base64-string. Turbo Module via JSI verwijdert een deel van deze beperkingen.
Meestal niet — Native Module API is stabiel en achterwaarts compatibel. Bij de overgang naar de nieuwe architectuur (Turbo Module) wordt de module aangepast via decorators, maar bestaande code blijft werken.
Gebruik RCTEventEmitter in iOS of DeviceEventEmitter in Android. De module verzendt de gebeurtenis en de JS-kant abonneert zich via NativeEventEmitter uit react-native. Dit is handig voor streaminggegevens en sensorgebeurtenissen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook