Native Module est une classe Java ou Objective-C qui rend les API natives de la plateforme accessibles depuis JavaScript dans React Native. Chaque module est enregistré dans le Bridge et exporte des méthodes qui peuvent être appelées depuis le code JS comme des fonctions ordinaires. Selon Meta, 2024, Native Module reste le principal moyen d'intégrer du code plateforme dans les applications React Native.
Points clés
Native Module est un élément architectural de React Native qui permet d'exécuter du code dans le langage de la plateforme (Objective-C/Swift pour iOS, Java/Kotlin pour Android) et de retourner le résultat à JavaScript. Sans Native Module, il est impossible d'accéder aux capacités natives de l'appareil — caméra, GPS, accéléromètre, système de fichiers ou Bluetooth.
React Native est livré avec un ensemble de Native Modules intégrés : CameraRoll, AsyncStorage, Geolocation, NetInfo et d'autres. Cependant, pour des tâches spécifiques — intégration de SDK tiers, travail avec des capteurs matériels ou processus en arrière-plan — le développeur crée des modules personnalisés. Selon l'enquête State of React Native 2024, 67% des développeurs utilisent au moins un Native Module personnalisé dans leurs projets.
L'architecture de Native Module dépend de la version de React Native. Dans l'architecture classique (React Native 0.72 et antérieur), le module se connecte via le Bridge et communique avec JS de manière asynchrone via la sérialisation JSON. Dans la nouvelle architecture (React Native 0.76+), le module peut fonctionner comme Turbo Module, utilisant JSI pour un accès synchrone sans sérialisation.
La création d'un Native Module pour iOS commence par la déclaration d'une classe Objective-C qui implémente le protocole RCTBridgeModule. La macro RCT_EXPORT_MODULE enregistre le module dans le Bridge, et RCT_EXPORT_METHOD exporte une méthode disponible depuis JavaScript.
// ImageCompressor.m — Native Module pour 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
La méthode compressImage prend un chemin d'image et une qualité de compression (0.0–1.0), traite les données côté natif et retourne le chemin vers le fichier compressé. L'avantage clé est que la compression est effectuée par du code natif, ce qui est significativement plus rapide et plus efficace en mémoire que l'opération équivalente en JavaScript.
Pour les modules Swift, l'annotation @objc est utilisée avant la classe et les méthodes pour les rendre disponibles au runtime Objective-C avec lequel le Bridge travaille. La classe doit hériter de NSObject et implémenter RCTBridgeModule.
// ImageCompressor.swift — Module Swift Natif
@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)
}
}
Sur Android, un Native Module est créé comme une classe Java qui étend ReactContextBaseJavaModule. L'annotation @ReactMethod exporte la méthode vers le Bridge. L'interface Promise de com.facebook.react.bridge est utilisée pour retourner les résultats.
// ImageCompressorModule.java — Native Module pour 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());
}
}
}
La méthode getName() retourne le nom du module sous lequel il sera accessible depuis JavaScript. Dans l'exemple ci-dessus, le module est enregistré sous le nom ImageCompressor. L'annotation @ReactMethod indique au Bridge que la méthode doit être exportée. Important : les méthodes doivent être void et n'accepter que les types pris en charge par le Bridge : String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Après avoir créé la classe du module, elle doit être enregistrée dans le paquet de l'application. Pour cela, une classe implémentant ReactPackage est créée et ajoutée à la liste des modules dans la méthode createNativeModules.
// ImageCompressorPackage.java — enregistrement du 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();
}
}
Après avoir créé les modules pour les deux plateformes, ils doivent être enregistrés dans React Native. Pour Android, le paquet est ajouté dans MainApplication.java dans la méthode getPackages(). Pour iOS, le module est automatiquement enregistré via la macro RCT_EXPORT_MODULE, mais l'enregistrement manuel dans AppDelegate.mm peut également être utilisé.
// MainApplication.java — ajout du paquet à 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;
}
};
}
Après l'enregistrement, le module devient disponible en JavaScript via NativeModules. React Native utilise automatiquement le nom du module spécifié dans getName() pour Android ou RCT_EXPORT_MODULE pour iOS.
// Utilisation de Native Module depuis 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('Compressé :', result);
return result;
} catch (error) {
console.error('Échec de la compression :', error);
throw error;
}
}
La comparaison du Native Module classique avec Turbo Module aide à comprendre quelle approche choisir pour un nouveau projet. Les deux mécanismes fournissent un accès au code natif, mais diffèrent fondamentalement par l'architecture et les performances.
| Caractéristique | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Communication | Asynchrone via JSON | Synchrone via JSI |
| Sérialisation | JSON à chaque appel | Sans copie de données |
| Typage | Manuel, sans génération | Automatique via Codegen |
| Chargement | À l'initialisation de l'application | Chargement différé (lazy load) |
| Compatibilité | Toutes les versions de React Native | React Native 0.73+ |
Pour les projets existants sur React Native 0.72 et antérieur, les Native Modules classiques restent le choix principal. Pour les nouveaux projets, Turbo Module est recommandé, surtout si des performances élevées sont nécessaires pour des appels fréquents à des méthodes natives. Avec la mise à jour progressive de React Native, la communauté évolue vers une transition complète vers la nouvelle architecture.
Questions fréquentes
Oui, le Bridge prend en charge les callbacks. Au lieu d'une Promise, on peut utiliser les fonctions callback RCTResponseSenderBlock dans iOS et Callback dans Android. Cependant, Promise est considérée comme la norme moderne et est recommandée pour les nouveaux modules.
Un Native Module se débogue comme du code natif ordinaire — placez des points d'arrêt dans Xcode ou Android Studio. Pour iOS, utilisez le schéma de build avec React Native ; pour Android, utilisez la configuration Debug. Le point d'entrée sont les méthodes appelées depuis JS.
Native Module via le Bridge ne prend pas en charge les données binaires (NSData/byte[]), les objets personnalisés et les fonctions. Pour les images, utilisez un chemin de fichier ou une chaîne base64. Turbo Module via JSI supprime certaines de ces limitations.
Généralement non — l'API Native Module est stable et rétrocompatible. Lors de la transition vers la nouvelle architecture (Turbo Module), le module est adapté via des décorateurs, mais le code existant continue de fonctionner.
Utilisez RCTEventEmitter dans iOS ou DeviceEventEmitter dans Android. Le module envoie un événement, et la partie JS s'abonne via NativeEventEmitter de react-native. C'est utile pour les données en streaming et les événements de capteurs.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi