Conexión end to end de un módulo Turbo dentro de la aplicación con React Native Codegen
Define una especificación tipada, ejecuta codegen e implementa un módulo Turbo en iOS y Android con métodos síncronos, Promise, callback y emisor de eventos.
La nueva arquitectura de React Native reemplaza el puente asíncrono por Turbo Modules, que JavaScript llama a través de JSI mediante enlaces generados y tipados. El resultado es una menor carga por llamadas y un contrato verificado mediante codegen en lugar de por convención. Esta guía crea un módulo dentro de una aplicación (sin paquete npm) y aborda los tres estilos de llamadas que utilizará en la práctica: un valor de retorno síncrono, resultados asíncronos a través de una Promise o callback, y un flujo de eventos.
Escribiendo la especificación tipada
Cree una carpeta specs en la raíz del proyecto para almacenar los esquemas de los módulos:
/your-app
/specs
Dentro de él, agregue NativeShowCase.ts. Codegen solo detecta los archivos de especificación cuyos nombres comienzan con Native. La especificación a continuación declara un método por estilo: haveCameraFlash devuelve de forma síncrona, toggleFlashLight devuelve una Promise, fetchUser acepta un callback, y evenListenerCalled es un emisor de eventos que transporta pares clave/valor. getEnforcing lanza un error al iniciar si falta el componente nativo, lo que permite detectar errores de conexión temprano. Los emisores de eventos tipados en las especificaciones son una adición reciente, así que verifique si su versión de React Native es compatible con CodegenTypes.EventEmitter.
import type { CodegenTypes, TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";
export type KeyValuePair = {
key: string;
value: string;
};
export interface Spec extends TurboModule {
toggleFlashLight(): Promise<boolean>;
haveCameraFlash(): boolean;
fetchUser(
id: string,
callback: (user: { id: string; name: string }) => void
): void;
readonly evenListenerCalled: CodegenTypes.EventEmitter<KeyValuePair>;
}
export default TurboModuleRegistry.getEnforcing<Spec>("NativeShowCase");
Este archivo es la única fuente de verdad; las vinculaciones de ambas plataformas se generan a partir de él.
Configuración y ejecución de codegen
Agregue un bloque codegenConfig a package.json. jsSrcsDir debe coincidir con la carpeta de especificaciones, y javaPackageName determina dónde se generarán las clases para Android:
"codegenConfig": {
"name": "NativeShowCase",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.nativeshowcase"
}
}
Luego genere los artefactos. En Android, ejecute la tarea Gradle:
cd android && ./gradlew generateCodegenArtifactsFromSchema
En iOS, codegen se ejecuta como parte de la instalación de pods:
cd ios && pod install
Ahora cuenta con interfaces, encabezados y código de enlace generados para ambas plataformas.
Implementación del módulo en iOS
Abra el .xcworkspace en Xcode, cree un grupo NativeShowCase y agregue una clase Objective-C llamada RCTNativeShowCase. Cambie el nombre del archivo .m a .mm: Los Turbo Modules necesitan Objective-C++ para interoperar con la capa JSI de C++.
El encabezado importa el módulo generado y hereda de la clase base generada, al tiempo que adopta el protocolo generado:
#import <Foundation/Foundation.h>
#import <NativeShowCase/NativeShowCase.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTNativeShowCase : NativeShowCaseSpecBase<NativeShowCaseSpec>
@end
NS_ASSUME_NONNULL_END
La implementación devuelve el nombre del módulo, entrega a React Native un objeto JSI proveniente de getTurboModule, e implementa cada método especificado. toggleFlashLight resuelve la Promise, fetchUser invoca la función de callback, y haveCameraFlash devuelve inmediatamente pero inicia un temporizador que emite diez eventos a través del método generado emitEvenListenerCalled.
#import "RCTNativeShowCase.h"
@interface RCTNativeShowCase()
@property (nonatomic, strong) NSTimer *myTimer;
@property (nonatomic, assign) int count;
@end
@implementation RCTNativeShowCase
+ (NSString *)moduleName {
return @"NativeShowCase";
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeShowCaseSpecJSI>(params);
}
- (void)toggleFlashLight:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
resolve(@(false));
}
- (nonnull NSNumber *)haveCameraFlash {
[self startTimer];
return [NSNumber numberWithBool:true];
}
- (void)fetchUser:(nonnull NSString *)userId callback:(nonnull RCTResponseSenderBlock)callback {
callback(@[@{@"key": userId, @"value": @"John"}]);
}
-(void)startTimer {
if(self.myTimer) {
[self stopTimer];
}
self.count = 0;
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateCount)
userInfo:nil
repeats:YES];
}
- (void)updateCount {
self.count += 1;
[self emitEvenListenerCalled:@{@"key": @"count", @"value": @(_count)}];
if (self.count == 10) {
[self stopTimer];
}
}
- (void)stopTimer {
[self.myTimer invalidate];
self.myTimer = nil;
}
@end
Tenga en cuenta que esta versión para iOS de fetchUser envía campos key/value, mientras que las especificaciones prometen id y name; debe alinearlos en el código real, ya que codegen no valida la estructura de los datos de las funciones de callback en tiempo de ejecución.
Implementación del módulo en Android
Cree el paquete com.nativeshowcase declarado en codegenConfig. La clase del módulo extiende la clase generada NativeShowCaseSpec y sobrescribe cada método: activa o desactiva una bandera y resuelve la Promise, ejecuta un CountDownTimer que emite los segundos restantes en cada actualización, y crea un mapa para la función de callback.
package com.nativeshowcase
import android.os.CountDownTimer
import android.widget.Toast
import com.facebook.fbreact.specs.NativeShowCaseSpec
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
class NativeShowCaseModule(reactContext: ReactApplicationContext) :
NativeShowCaseSpec(reactContext) {
var timer: CountDownTimer? = null
var lightOn = false
override fun toggleFlashLight(promise: Promise?) {
Toast.makeText(reactApplicationContext, "Let's turn on flash light", Toast.LENGTH_LONG)
.show()
lightOn = !lightOn
promise?.resolve(lightOn)
}
override fun haveCameraFlash(): Boolean {
if (timer != null) {
timer?.cancel()
timer = null
}
timer = object : CountDownTimer(10000, 1000) {
override fun onTick(millisUntilFinished: Long) {
val eventData = Arguments.createMap().apply {
putString("key", "count")
putInt("value", (millisUntilFinished / 1000).toInt())
}
emitEvenListenerCalled(eventData)
}
override fun onFinish() {
timer?.cancel()
timer = null
}
}
timer?.start()
return true
}
override fun fetchUser(id: String?, callback: Callback?) {
val eventData = Arguments.createMap().apply {
putString("id", id)
putString("name", "John")
}
callback.let { it?.invoke(eventData) }
}
companion object {
const val NAME = "NativeShowCase"
}
override fun getName() = NAME
}
Una clase de paquete basada en BaseReactPackage indica a React Native cómo instanciar el módulo. La bandera isTurboModule = true en su ReactModuleInfo es lo que hace que se procese a través del nuevo sistema.
package com.nativeshowcase
import com.facebook.react.BaseReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
class NativeShowCasePackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
if (name == NativeShowCaseModule.NAME) return NativeShowCaseModule(reactContext)
return null
}
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
mapOf(
NativeShowCaseModule.NAME to ReactModuleInfo(
name = NativeShowCaseModule.NAME,
className = NativeShowCaseModule.NAME,
canOverrideExistingModule = false,
needsEagerInit = true,
isCxxModule = false,
isTurboModule = true
)
)
}
}
Finalmente, registre el paquete en MainApplication.kt añadiéndolo a la lista de paquetes con enlace automático:
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList =
PackageList(this).packages.apply {
add(NativeShowCasePackage())
},
)
}
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}
Llamar al módulo desde React
Importe la exportación predeterminada de la especificación y úsela como cualquier otro objeto. El efecto se suscribe al emisor de eventos y devuelve una función de limpieza que elimina el oyente al desmontarse; un botón llama al método síncrono, mientras que el otro utiliza los métodos Promise y callback.
import { StatusBar, StyleSheet, useColorScheme, View, Button } from 'react-native';
import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context';
import NativeShowCase from './specs/NativeShowCase';
import { useEffect } from 'react';
function App() {
const isDarkMode = useColorScheme() === 'dark';
return (
<SafeAreaProvider>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<AppContent />
</SafeAreaProvider>
);
}
function AppContent() {
useEffect(() => {
const listener = NativeShowCase.evenListenerCalled(pair => {
console.log('Event received:', pair);
});
return () => listener.remove();
}, []);
return (
<View style={styles.container}>
<Button
title="Have Camera Flash?"
onPress={() => {
console.log('Have Camera Flash:', NativeShowCase.haveCameraFlash());
}}
/>
<Button
title="Toggle Flashlight"
onPress={() => {
NativeShowCase.toggleFlashLight()
.then(isOn => console.log('Flashlight:', isOn))
.catch(err => console.error(err));
NativeShowCase.fetchUser('123', user => {
console.log('Fetched user:', user);
});
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
});
export default App;
Use los métodos síncronos con moderación: bloquean el hilo de JavaScript hasta que la función nativa devuelve un resultado, así que guárdelos para búsquedas simples. Para conocer otro enfoque de vinculación, consulte cómo Nitro Modules se comparan con Turbo Modules.
Puntos clave
- La especificación de TypeScript es el contrato; codegen deriva las interfaces nativas para ambas plataformas a partir de ella.
- Nombré los archivos de especificación con el prefijo
Nativey mantengacodegenConfigsincronizado con las carpetas y paquetes.
.mm; los paquetes de Android deben marcar los módulos con isTurboModule.Lecturas relacionadas
- Enrutamiento de notificaciones push a través de enlaces profundos en React Native — Configure enlaces universales y enlaces de aplicación, conecte Pusher Beams a una app de React Native y asocie URLs web con pantallas nativas para que un toque en una notificación abra el lugar correcto.
- Planificación de la actualización del Expo SDK 58: iOS 27, React Native 0.88 y nuevas herramientas — Un recorrido práctico por la versión beta del Expo SDK 58: qué cambios hay en iOS 27 y React Native 0.88, qué funciones son experimentales y cómo probar la actualización de forma segura.
- Tratando las carpetas nativas como resultado de la construcción con Expo Prebuild y CNG — Cómo Continuous Native Generation permite que una aplicación de Expo utilice módulos nativos personalizados, plugins de configuración y secretos de EAS sin tener que guardar o editar manualmente las carpetas de iOS y Android.
- Notificaciones en React Native: Permisos, canales y el ciclo de vida de FCM — Aprenda cómo los permisos, los canales de Android, los tokens de FCM y los manejadores para estados en primer plano, segundo plano y de cierre se integran en un sistema de notificaciones de React Native con Notifee.
- Actualizaciones OTA autogestionadas para React Native bare con hot-updater y Supabase — Configure actualizaciones por aire mediante JavaScript en una aplicación React Native bare con hot-updater y Supabase: inicialización, un gancho para actualizaciones silenciosas, canales, scripts de despliegue y funcionalidad de reversión.