Native Module é uma classe Java ou Objective-C que torna as APIs nativas da plataforma acessíveis a partir do JavaScript no React Native. Cada módulo é registrado no Bridge e exporta métodos que podem ser chamados do código JS como funções comuns. De acordo com Meta, 2024, o Native Module continua sendo a principal forma de integrar código de plataforma em aplicações React Native.
Pontos principais
Native Module é um elemento arquitetônico do React Native que permite executar código na linguagem da plataforma (Objective-C/Swift para iOS, Java/Kotlin para Android) e retornar o resultado para JavaScript. Sem um Native Module é impossível acessar as capacidades nativas do dispositivo — câmera, GPS, acelerômetro, sistema de arquivos ou Bluetooth.
O React Native vem com um conjunto de Native Modules integrados: CameraRoll, AsyncStorage, Geolocation, NetInfo e outros. No entanto, para tarefas específicas — integração de SDKs de terceiros, trabalho com sensores de hardware ou processos em segundo plano — o desenvolvedor cria módulos personalizados. De acordo com a pesquisa State of React Native 2024, 67% dos desenvolvedores usam pelo menos um Native Module personalizado em seus projetos.
A arquitetura do Native Module depende da versão do React Native. Na arquitetura clássica (React Native 0.72 e anteriores) o módulo se conecta através do Bridge e se comunica com JS de forma assíncrona via serialização JSON. Na nova arquitetura (React Native 0.76+) o módulo pode funcionar como Turbo Module, usando JSI para acesso síncrono sem serialização.
A criação de um Native Module para iOS começa com a declaração de uma classe Objective-C que implementa o protocolo RCTBridgeModule. A macro RCT_EXPORT_MODULE registra o módulo no Bridge, e RCT_EXPORT_METHOD exporta um método disponível a partir do 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
O método compressImage recebe um caminho de imagem e qualidade de compressão (0.0–1.0), processa os dados no lado nativo e retorna o caminho para o arquivo comprimido. A vantagem principal é que a compressão é realizada por código nativo, que é significativamente mais rápido e eficiente em memória do que a operação equivalente em JavaScript.
Para módulos Swift, a anotação @objc é usada antes da classe e dos métodos para torná-los disponíveis para o runtime Objective-C com o qual o Bridge trabalha. A classe deve herdar de NSObject e implementar RCTBridgeModule.
// ImageCompressor.swift — Módulo Swift Nativo
@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)
}
}
No Android, um Native Module é criado como uma classe Java que estende ReactContextBaseJavaModule. A anotação @ReactMethod exporta o método para o Bridge. A interface Promise de com.facebook.react.bridge é usada para retornar resultados.
// ImageCompressorModule.java — Native Module para 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());
}
}
}
O método getName() retorna o nome do módulo sob o qual ele será acessível a partir do JavaScript. No exemplo acima, o módulo é registrado como ImageCompressor. A anotação @ReactMethod indica ao Bridge que o método deve ser exportado. Importante: os métodos devem ser void e aceitar apenas tipos suportados pelo Bridge: String, Boolean, Integer, Double, ReadableArray, ReadableMap, Promise.
Após criar a classe do módulo, ele deve ser registrado no pacote da aplicação. Para isso, uma classe que implementa ReactPackage é criada e adicionada à lista de módulos no método createNativeModules.
// ImageCompressorPackage.java — registro do 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();
}
}
Após criar os módulos para ambas as plataformas, eles devem ser registrados no React Native. Para Android, o pacote é adicionado no MainApplication.java no método getPackages(). Para iOS, o módulo é registrado automaticamente através da macro RCT_EXPORT_MODULE, mas o registro manual no AppDelegate.mm também pode ser usado.
// MainApplication.java — adicionando pacote ao 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;
}
};
}
Após o registro, o módulo fica disponível em JavaScript através de NativeModules. O React Native usa automaticamente o nome do módulo especificado no getName() para Android ou RCT_EXPORT_MODULE para iOS.
// Usando Native Module do 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('Falha na compressão:', error);
throw error;
}
}
A comparação do Native Module clássico com o Turbo Module ajuda a entender qual abordagem escolher para um novo projeto. Ambos os mecanismos fornecem acesso ao código nativo, mas diferem fundamentalmente em arquitetura e desempenho.
| Característica | Native Module (Bridge) | Turbo Module (JSI) |
|---|---|---|
| Comunicação | Assíncrona via JSON | Síncrona via JSI |
| Serialização | JSON em cada chamada | Sem cópia de dados |
| Tipagem | Manual, sem geração | Automática via Codegen |
| Carregamento | Na inicialização da aplicação | Carregamento lento (lazy load) |
| Compatibilidade | Todas as versões do React Native | React Native 0.73+ |
Para projetos existentes no React Native 0.72 e anteriores, os Native Modules clássicos continuam sendo a escolha principal. Para novos projetos, o Turbo Module é recomendado, especialmente se alto desempenho for necessário em chamadas frequentes a métodos nativos. Com a atualização gradual do React Native, a comunidade está avançando para uma transição completa para a nova arquitetura.
Perguntas frequentes
Sim, o Bridge suporta callbacks. Em vez de Promise, podem ser usadas funções de callback RCTResponseSenderBlock no iOS e Callback no Android. No entanto, Promise é considerado o padrão moderno e é recomendado para novos módulos.
Um Native Module é depurado como código nativo normal — defina pontos de interrupção no Xcode ou Android Studio. Para iOS, use o esquema de compilação com React Native; para Android, use a configuração Debug. O ponto de entrada são os métodos chamados do JS.
Native Module através do Bridge não suporta dados binários (NSData/byte[]), objetos personalizados e funções. Para imagens, use um caminho de arquivo ou string base64. O Turbo Module via JSI remove algumas dessas limitações.
Geralmente não — a API do Native Module é estável e compatível com versões anteriores. Ao fazer a transição para a nova arquitetura (Turbo Module), o módulo é adaptado através de decoradores, mas o código existente continua funcionando.
Use RCTEventEmitter no iOS ou DeviceEventEmitter no Android. O módulo envia um evento e o lado JS se inscreve através do NativeEventEmitter do react-native. Isso é útil para dados em streaming e eventos de sensores.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também