Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Módulos nativos
Es posible que tu código de aplicación React Native necesite interactuar con APIs nativas de plataforma que no están proporcionadas por React Native ni por bibliotecas existentes. Puedes escribir tú mismo el código de integración usando un Módulo Nativo Turbo. Esta guía te mostrará cómo escribir uno.
Los pasos básicos son:
-
definir una especificación JavaScript tipada usando uno de los lenguajes de anotación de tipos más populares: Flow o TypeScript;
-
configurar tu sistema de gestión de dependencias para ejecutar Codegen, que convierte la especificación en interfaces de lenguaje nativo;
-
escribir tu código de aplicación usando tu especificación; y
-
escribir tu código de plataforma nativa usando las interfaces generadas para implementar y conectar tu código nativo al entorno de ejecución de React Native.
Trabajemos cada uno de estos pasos construyendo un ejemplo de Módulo Nativo Turbo. El resto de esta guía asume que has creado tu aplicación ejecutando el comando:
npx @react-native-community/cli@latest init TurboModuleExample --version 0.76.0
Almacenamiento persistente nativo
Esta guía te mostrará cómo escribir una implementación de la API Web Storage: localStorage. La API es familiar para desarrolladores React que podrían estar escribiendo código de aplicación en tu proyecto.
Para que esto funcione en móviles, necesitamos usar APIs de Android e iOS:
-
Android: SharedPreferences, y
-
iOS: NSUserDefaults.
1. Declarar la especificación tipada
React Native proporciona una herramienta llamada Codegen, que toma una especificación escrita en TypeScript o Flow y genera código específico de plataforma para Android e iOS. La especificación declara los métodos y tipos de datos que pasarán entre tu código nativo y el entorno de ejecución JavaScript de React Native. Un Módulo Nativo Turbo incluye tanto tu especificación, el código nativo que escribes, como las interfaces de Codegen generadas a partir de tu especificación.
Para crear un archivo de especificaciones:
-
Dentro de la carpeta raíz de tu aplicación, crea una nueva carpeta llamada
specs. -
Crea un nuevo archivo llamado
NativeLocalStorage.ts.
Puedes ver todos los tipos que puedes usar en tu especificación y los tipos nativos generados en la documentación del Apéndice.
:::información
Si deseas cambiar el nombre de tu módulo y el archivo de especificaciones relacionado, asegúrate de siempre usar el prefijo 'Native' (por ejemplo, NativeStorage o NativeUsersDefault).
:::
Aquí hay una implementación de la especificación localStorage:
- TypeScript
- Flow
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
setItem(value: string, key: string): void;
getItem(key: string): string | null;
removeItem(key: string): void;
clear(): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeLocalStorage',
);
import type {TurboModule} from 'react-native';
import {TurboModule, TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
setItem(value: string, key: string): void;
getItem(key: string): ?string;
removeItem(key: string): void;
clear(): void;
}
2. Configurar la ejecución de Codegen
La especificación es utilizada por las herramientas Codegen de React Native para generar interfaces específicas de plataforma y código repetitivo. Para hacer esto, Codegen necesita saber dónde encontrar nuestra especificación y qué hacer con ella. Actualiza tu package.json para incluir:
"start": "react-native start",
"test": "jest"
},
"codegenConfig": {
"name": "NativeLocalStorageSpec",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.nativelocalstorage"
}
},
"dependencies": {
Con todo configurado para Codegen, necesitamos preparar nuestro código nativo para conectarse con el código generado.
- Android
- iOS
Codegen is executed through the generateCodegenArtifactsFromSchema Gradle task:
cd android
./gradlew generateCodegenArtifactsFromSchema
BUILD SUCCESSFUL in 837ms
14 actionable tasks: 3 executed, 11 up-to-date
This is automatically run when you build your Android application.
Codegen is run as part of the script phases that's automatically added to the project generated by CocoaPods.
cd ios
bundle install
bundle exec pod install
The output will look like this:
...
Framework build type is static library
[Codegen] Adding script_phases to ReactCodegen.
[Codegen] Generating ./build/generated/ios/ReactCodegen.podspec.json
[Codegen] Analyzing /Users/me/src/TurboModuleExample/package.json
[Codegen] Searching for codegen-enabled libraries in the app.
[Codegen] Found TurboModuleExample
[Codegen] Searching for codegen-enabled libraries in the project dependencies.
[Codegen] Found react-native
...
3. Escribir código de aplicación usando el Módulo Nativo Turbo
Usando NativeLocalStorage, aquí hay un App.tsx modificado que incluye un texto que queremos persistir, un campo de entrada y algunos botones para actualizar este valor.
El TurboModuleRegistry admite 2 modos para recuperar un Módulo Nativo Turbo:
-
get<T>(name: string): T | nullque devolveránullsi el Módulo Nativo Turbo no está disponible. -
getEnforcing<T>(name: string): Tque lanzará una excepción si el Módulo Nativo Turbo no está disponible. Esto asume que el módulo siempre está disponible.
import React from 'react';
import {
SafeAreaView,
StyleSheet,
Text,
TextInput,
Button,
} from 'react-native';
import NativeLocalStorage from './specs/NativeLocalStorage';
const EMPTY = '<empty>';
function App(): React.JSX.Element {
const [value, setValue] = React.useState<string | null>(null);
const [editingValue, setEditingValue] = React.useState<
string | null
>(null);
React.useEffect(() => {
const storedValue = NativeLocalStorage?.getItem('myKey');
setValue(storedValue ?? '');
}, []);
function saveValue() {
NativeLocalStorage?.setItem(editingValue ?? EMPTY, 'myKey');
setValue(editingValue);
}
function clearAll() {
NativeLocalStorage?.clear();
setValue('');
}
function deleteValue() {
NativeLocalStorage?.removeItem('myKey');
setValue('');
}
return (
<SafeAreaView style={{flex: 1}}>
<Text style={styles.text}>
Current stored value is: {value ?? 'No Value'}
</Text>
<TextInput
placeholder="Enter the text you want to store"
style={styles.textInput}
onChangeText={setEditingValue}
/>
<Button title="Save" onPress={saveValue} />
<Button title="Delete" onPress={deleteValue} />
<Button title="Clear" onPress={clearAll} />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
text: {
margin: 10,
fontSize: 20,
},
textInput: {
margin: 10,
height: 40,
borderColor: 'black',
borderWidth: 1,
paddingLeft: 5,
paddingRight: 5,
borderRadius: 5,
},
});
export default App;
4. Escribir tu código de plataforma nativa
Con todo preparado, vamos a comenzar a escribir código de plataforma nativa. Hacemos esto en 2 partes:
Esta guía te muestra cómo crear un Turbo Native Module que solo funciona con la Nueva Arquitectura. Si necesitas admitir tanto la Nueva Arquitectura como la Arquitectura Legacy, consulta nuestra guía de compatibilidad con versiones anteriores.
- Android
- iOS
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Ahora es momento de escribir código de plataforma Android para asegurar que localStorage persista después de cerrar la aplicación.
El primer paso es implementar la interfaz generada NativeLocalStorageSpec:
- Java
- Kotlin
package com.nativelocalstorage;
import android.content.Context;
import android.content.SharedPreferences;
import com.nativelocalstorage.NativeLocalStorageSpec;
import com.facebook.react.bridge.ReactApplicationContext;
public class NativeLocalStorageModule extends NativeLocalStorageSpec {
public static final String NAME = "NativeLocalStorage";
public NativeLocalStorageModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public void setItem(String value, String key) {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putString(key, value);
editor.apply();
}
@Override
public String getItem(String key) {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
String username = sharedPref.getString(key, null);
return username;
}
@Override
public void removeItem(String key) {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
sharedPref.edit().remove(key).apply();
}
@Override
public void clear() {
SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
sharedPref.edit().clear().apply();
}
}
package com.nativelocalstorage
import android.content.Context
import android.content.SharedPreferences
import com.nativelocalstorage.NativeLocalStorageSpec
import com.facebook.react.bridge.ReactApplicationContext
class NativeLocalStorageModule(reactContext: ReactApplicationContext) : NativeLocalStorageSpec(reactContext) {
override fun getName() = NAME
override fun setItem(value: String, key: String) {
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putString(key, value)
editor.apply()
}
override fun getItem(key: String): String? {
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val username = sharedPref.getString(key, null)
return username.toString()
}
override fun removeItem(key: String) {
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.remove(key)
editor.apply()
}
override fun clear() {
val sharedPref = getReactApplicationContext().getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.clear()
editor.apply()
}
companion object {
const val NAME = "NativeLocalStorage"
}
}
Luego necesitamos crear NativeLocalStoragePackage. Este provee un objeto para registrar nuestro Módulo en el entorno de ejecución de React Native, envolviéndolo como un Paquete Nativo Base:
- Java
- Kotlin
package com.nativelocalstorage;
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;
import java.util.HashMap;
import java.util.Map;
public class NativeLocalStoragePackage extends BaseReactPackage {
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(NativeLocalStorageModule.NAME)) {
return new NativeLocalStorageModule(reactContext);
} else {
return null;
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
Map<String, ReactModuleInfo> map = new HashMap<>();
map.put(NativeLocalStorageModule.NAME, new ReactModuleInfo(
NativeLocalStorageModule.NAME, // name
NativeLocalStorageModule.NAME, // className
false, // canOverrideExistingModule
false, // needsEagerInit
false, // isCXXModule
true // isTurboModule
));
return map;
}
};
}
}
package com.nativelocalstorage
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 NativeLocalStoragePackage : BaseReactPackage() {
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
if (name == NativeLocalStorageModule.NAME) {
NativeLocalStorageModule(reactContext)
} else {
null
}
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
mapOf(
NativeLocalStorageModule.NAME to ReactModuleInfo(
name = NativeLocalStorageModule.NAME,
className = NativeLocalStorageModule.NAME,
canOverrideExistingModule = false,
needsEagerInit = false,
isCxxModule = false,
isTurboModule = true
)
)
}
}
Finalmente, debemos indicarle a React Native en nuestra aplicación principal cómo encontrar este Package. A esto lo llamamos "registrar" el paquete en React Native.
En este caso, lo agregas para que sea devuelto por el método getPackages.
Más adelante aprenderás a distribuir tus Módulos Nativos como paquetes npm, que nuestras herramientas de compilación enlazarán automáticamente.
- Java
- Kotlin
package com.inappmodule;
import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactHost;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactHost;
import com.facebook.react.defaults.DefaultReactNativeHost;
import com.facebook.soloader.SoLoader;
import com.nativelocalstorage.NativeLocalStoragePackage;
import java.util.ArrayList;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost reactNativeHost = new DefaultReactNativeHost(this) {
@Override
public List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new NativeLocalStoragePackage());
return packages;
}
@Override
public String getJSMainModuleName() {
return "index";
}
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
public boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
public boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_ENABLED;
}
};
@Override
public ReactHost getReactHost() {
return DefaultReactHost.getDefaultReactHost(getApplicationContext(), reactNativeHost);
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
}
}
}
package com.inappmodule
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
import com.nativelocalstorage.NativeLocalStoragePackage
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(NativeLocalStoragePackage())
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
SoLoader.init(this, false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
}
}
Ahora puedes compilar y ejecutar tu código en un emulador:
- npm
- Yarn
npm run android
yarn run android
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Es momento de escribir código nativo para iOS que garantice que localStorage persista después de cerrar la aplicación.
Prepara tu proyecto de Xcode
Debes preparar tu proyecto iOS usando Xcode. Tras completar estos 6 pasos, tendrás RCTNativeLocalStorage implementando la interfaz generada NativeLocalStorageSpec.
- Abre el espacio de trabajo de Xcode generado por CocoaPods:
cd ios
open TurboModuleExample.xcworkspace
- Haz clic derecho en la aplicación y selecciona
New Group, nombrando el nuevo grupoNativeLocalStorage.
- Dentro del grupo
NativeLocalStorage, creaNew→File from Template.
- Utiliza la plantilla
Cocoa Touch Class.
- Nombra la clase Cocoa Touch como
RCTNativeLocalStorageusando el lenguajeObjective-C.
- Cambia el nombre de
RCTNativeLocalStorage.m→RCTNativeLocalStorage.mmconvirtiéndolo en un archivo Objective-C++.
Implementa localStorage con NSUserDefaults
Comienza actualizando RCTNativeLocalStorage.h:
// RCTNativeLocalStorage.h
// TurboModuleExample
#import <Foundation/Foundation.h>
#import <NativeLocalStorageSpec/NativeLocalStorageSpec.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTNativeLocalStorage : NSObject
@interface RCTNativeLocalStorage : NSObject <NativeLocalStorageSpec>
@end
Luego actualiza la implementación para usar NSUserDefaults con un suite name personalizado.
// RCTNativeLocalStorage.m
// TurboModuleExample
#import "RCTNativeLocalStorage.h"
static NSString *const RCTNativeLocalStorageKey = @"local-storage";
@interface RCTNativeLocalStorage()
@property (strong, nonatomic) NSUserDefaults *localStorage;
@end
@implementation RCTNativeLocalStorage
RCT_EXPORT_MODULE(NativeLocalStorage)
- (id) init {
if (self = [super init]) {
_localStorage = [[NSUserDefaults alloc] initWithSuiteName:RCTNativeLocalStorageKey];
}
return self;
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeLocalStorageSpecJSI>(params);
}
- (NSString * _Nullable)getItem:(NSString *)key {
return [self.localStorage stringForKey:key];
}
- (void)setItem:(NSString *)value
key:(NSString *)key {
[self.localStorage setObject:value forKey:key];
}
- (void)removeItem:(NSString *)key {
[self.localStorage removeObjectForKey:key];
}
- (void)clear {
NSDictionary *keys = [self.localStorage dictionaryRepresentation];
for (NSString *key in keys) {
[self removeItem:key];
}
}
@end
Aspectos importantes a considerar:
-
RCT_EXPORT_MODULEexporta y registra el módulo usando el identificador que usaremos para acceder en el entorno JavaScript:NativeLocalStorage. Consulta esta documentación para más detalles. -
Puedes usar Xcode para saltar al
@protocol NativeLocalStorageSpecde Codegen. También puedes generar stubs automáticamente con Xcode.
Compila y ejecuta tu código en un simulador
- npm
- Yarn
npm run ios
yarn run ios