Native Module es una clase Java u Objective-C que hace que las API nativas de la plataforma sean accesibles desde JavaScript en React Native. Cada módulo se registra en el Bridge y exporta métodos que se pueden llamar desde código JS como funciones normales. Según Meta, 2024, Native Module sigue siendo la forma principal de integrar código de plataforma en aplicaciones React Native.
Puntos clave
Native Module es un elemento arquitectónico de React Native que permite ejecutar código en el lenguaje de la plataforma (Objective-C/Swift para iOS, Java/Kotlin para Android) y devolver el resultado a JavaScript. Sin un Native Module es imposible acceder a las capacidades nativas del dispositivo — cámara, GPS, acelerómetro, sistema de archivos o Bluetooth.
React Native viene con un conjunto de Native Modules incorporados: CameraRoll, AsyncStorage, Geolocation, NetInfo y otros. Sin embargo, para tareas específicas — integración de SDK de terceros, trabajo con sensores de hardware o procesos en segundo plano — el desarrollador crea módulos personalizados. Según la encuesta State of React Native 2024, el 67% de los desarrolladores utiliza al menos un Native Module personalizado en sus proyectos.
La arquitectura de Native Module depende de la versión de React Native. En la arquitectura clásica (React Native 0.72 y anteriores) el módulo se conecta a través del Bridge y se comunica con JS de forma asíncrona mediante serialización JSON. En la nueva arquitectura (React Native 0.76+) el módulo puede funcionar como Turbo Module, utilizando JSI para acceso síncrono sin serialización.
La creación de un Native Module para iOS comienza con la declaración de una clase Objective-C que implementa el protocolo RCTBridgeModule. La macro RCT_EXPORT_MODULE registra el módulo en el Bridge, y RCT_EXPORT_METHOD exporta un método disponible desde JavaScript.
// ImageCompressor.m — Native Module para 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
El método compressImage recibe una ruta de imagen y calidad de compresión (0.0–1.0), procesa los datos en el lado nativo y devuelve la ruta al archivo comprimido. La ventaja clave es que la compresión se realiza mediante código nativo, que es significativamente más rápido y eficiente en memoria que la operación equivalente en JavaScript.
Para módulos Swift, se usa la anotación @objc antes de la clase y los métodos para que estén disponibles para el runtime de Objective-C con el que trabaja el Bridge. La clase debe heredar de NSObject e implementar RCTBridgeModule.
// ImageCompressor.swift — Módulo Swift Native
@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)
}
}
En Android, un Native Module se crea como una clase Java que extiende ReactContextBaseJavaModule. La anotación @ReactMethod exporta el método al Bridge. La interfaz Promise de com.facebook.react.bridge se utiliza para devolver resultados.
// ImageCompressorModule.java — Módulo Android Native
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());
}
}
}
El método getName() devuelve el nombre del módulo bajo el cual será accesible desde JavaScript. En el ejemplo anterior, el módulo se registra como ImageCompressor. La anotación @ReactMethod indica al Bridge que el método debe ser exportado. Importante: los métodos deben ser void y solo aceptar tipos compatibles con el Bridge: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Después de crear la clase del módulo, debe registrarse en el paquete de la aplicación. Para ello, se crea una clase que implementa ReactPackage y se añade a la lista de módulos en el método createNativeModules.
// ImageCompressorPackage.java — registro del módulo
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();
}
}
Después de crear los módulos para ambas plataformas, deben registrarse en React Native. Para Android, el paquete se añade en MainApplication.java en el método getPackages(). Para iOS, el módulo se registra automáticamente mediante la macro RCT_EXPORT_MODULE, pero también se puede usar el registro manual en AppDelegate.mm.
// MainApplication.java — agregando paquete a 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;
}
};
}
Después del registro, el módulo está disponible en JavaScript a través de NativeModules. React Native utiliza automáticamente el nombre del módulo especificado en getName() para Android o RCT_EXPORT_MODULE para iOS.
// Usando Native Module desde 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('Comprimido:', result);
return result;
} catch (error) {
console.error('Error de compresión:', error);
throw error;
}
}
La comparación del Native Module clásico con Turbo Module ayuda a entender qué enfoque elegir para un nuevo proyecto. Ambos mecanismos proporcionan acceso al código nativo, pero difieren fundamentalmente en arquitectura y rendimiento.
| Característica | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Comunicación | Asíncrona mediante JSON | Síncrona mediante JSI |
| Serialización | JSON en cada llamada | Sin copia de datos |
| Tipado | Manual, sin generación | Automático mediante Codegen |
| Carga | En la inicialización de la aplicación | Carga diferida (lazy load) |
| Compatibilidad | Todas las versiones de React Native | React Native 0.73+ |
Para proyectos existentes en React Native 0.72 y anteriores, los Native Modules clásicos siguen siendo la opción principal. Para nuevos proyectos, se recomienda Turbo Module, especialmente si se requiere alto rendimiento en llamadas frecuentes a métodos nativos. Con la actualización gradual de React Native, la comunidad avanza hacia una transición completa a la nueva arquitectura.
Preguntas frecuentes
Sí, el Bridge admite callbacks. En lugar de Promise, se pueden usar funciones callback RCTResponseSenderBlock en iOS y Callback en Android. Sin embargo, Promise se considera el estándar moderno y se recomienda para nuevos módulos.
Un Native Module se depura como código nativo normal — establezca puntos de interrupción en Xcode o Android Studio. Para iOS, use el esquema de compilación con React Native; para Android, use la configuración Debug. El punto de entrada son los métodos llamados desde JS.
Native Module a través del Bridge no admite datos binarios (NSData/byte[]), objetos personalizados ni funciones. Para imágenes, use una ruta de archivo o cadena base64. Turbo Module mediante JSI elimina algunas de estas limitaciones.
Generalmente no — la API de Native Module es estable y compatible con versiones anteriores. Al hacer la transición a la nueva arquitectura (Turbo Module), el módulo se adapta mediante decoradores, pero el código existente sigue funcionando.
Use RCTEventEmitter en iOS o DeviceEventEmitter en Android. El módulo envía un evento y la parte JS se suscribe mediante NativeEventEmitter de react-native. Esto es útil para datos en streaming y eventos de sensores.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también