Diez ejemplos de cuándo las soluciones solo JavaScript fallan y debes cruzar el límite nativo - la base que cada equipo de Expo SDK 57 necesita antes de agregar bibliotecas de terceros, plugins de configuración o módulos personalizados.
Comienza desde una aplicación Expo en blanco con una ruta de compilación de desarrollo lista - los módulos nativos no se ejecutan en Jest sin mocks y no pueden cargar código arbitrario en Expo Go:
Herramientas: Estos ejemplos se dirigen a Expo SDK 57 (expo ~57.0.4), React Native 0.86.0 y React 19.2.3. Empareja esta página con Reglas de Módulos Nativos antes de agregar cualquier paquete con código nativo.
Los fotogramas de cámara, fijaciones de GPS, flujos de acelerómetro y sensores biométricos leen hardware del dispositivo a través de APIs del SO. JavaScript no puede acceder a ellos sin un módulo nativo:
import { CameraView, useCameraPermissions } from "expo-camera";import { Button } from "react-native";export function ScanScreen() { const [permission, requestPermission] = useCameraPermissions(); if (!permission?.granted) { return <Button title="Allow camera" onPress={requestPermission} />; } return <CameraView style={{ flex: 1 }} facing="back" />;}
expo-camera envuelve AVFoundation (iOS) y CameraX (Android) - sin sustituto puro en JS
Los prompts de permisos requieren entradas en Info.plist y AndroidManifest - los plugins de configuración se encargan de esto
Expo Go incluye expo-camera; los SDKs de cámara arbitrarios no
Los timers y fetch se pausan cuando la aplicación está en segundo plano o se ha eliminado. La entrega de push, actualizaciones de ubicación y cargas en segundo plano necesitan programadores nativos:
import * as TaskManager from "expo-task-manager";import * as Location from "expo-location";const TASK_NAME = "background-location";TaskManager.defineTask(TASK_NAME, ({ data, error }) => { if (error) return; const { locations } = data as { locations: Location.LocationObject[] }; // Carga ubicaciones a tu API - la tarea nativa sigue disparándose});export async function startBackgroundLocation(): Promise<void> { await Location.startLocationUpdatesAsync(TASK_NAME, { accuracy: Location.Accuracy.Balanced, distanceInterval: 100, showsBackgroundLocationIndicator: true, });}
Los modos de segundo plano requieren derechos y flags de manifiesto - nunca edites manualmente después del prebuild de CNG
iOS mata aplicaciones que reclaman capacidad de segundo plano sin uso real - la revisión de App Store rechaza modos fantasma
Algunas vistas no pueden reproducirse fielmente en el layout de React Native - mapas, reproductores de video, renderizadores PDF y WebViews incrustan vistas nativas de plataforma dentro del árbol RN:
import MapView, { Marker } from "react-native-maps";export function StoreMap({ lat, lng }: { lat: number; lng: number }) { return ( <MapView style={{ flex: 1 }} initialRegion={{ latitude: lat, longitude: lng, latitudeDelta: 0.02, longitudeDelta: 0.02, }} > <Marker coordinate={{ latitude: lat, longitude: lng }} title="Store" /> </MapView> );}
Las vistas nativas participan en el árbol de sombra Fabric en Nueva Arquitectura - los props de layout aún aplican al contenedor
Los SDKs de mapas de terceros envían sus propios binarios nativos - evalúa la compatibilidad antes de npm install
HealthKit, NFC, CarPlay, atajos de Siri e intents de Android existen solo en una plataforma. La detección de características cierra JS; los módulos nativos implementan la llamada:
import { Platform } from "react-native";import * as LocalAuthentication from "expo-local-authentication";export async function biometricGate(): Promise<boolean> { if (Platform.OS === "web") return false; const compatible = await LocalAuthentication.hasHardwareAsync(); if (!compatible) return false; const enrolled = await LocalAuthentication.isEnrolledAsync(); if (!enrolled) return false; const result = await LocalAuthentication.authenticateAsync({ promptMessage: "Unlock to continue", }); return result.success;}
Los checks Platform.OS previenen crashes; no reemplazan la implementación nativa
Los módulos específicos de plataforma deben exponer una API unificada de TypeScript en tu capa de aplicación - oculta Platform.select dentro de servicios
Las transformaciones de imagen pesadas, codificación de video, criptografía e inferencia de ML bloquean el hilo de JS si se hacen en puro JavaScript. Los módulos nativos ejecutan trabajo en colas de fondo o rutas de GPU:
import * as ImageManipulator from "expo-image-manipulator";export async function resizeAvatar(uri: string): Promise<string> { const result = await ImageManipulator.manipulateAsync( uri, [{ resize: { width: 256 } }], { compress: 0.8, format: ImageManipulator.SaveFormat.JPEG } ); return result.uri;}
Hermes es rápido para la lógica de aplicación - no es un sustituto para Core Image, MediaCodec o SIMD C++
Perfila en el dispositivo: si los fotogramas de JS caen coincidiendo con tu función, mueve el trabajo a nativo o a un worker
Las hojas de compartición, widgets de pantalla de inicio, App Clips y enlaces universales se registran en la capa de aplicación nativa. JavaScript recibe eventos de enlace profundo; el código nativo posee el registro:
import * as Linking from "expo-linking";export function subscribeToDeepLinks(onUrl: (url: string) => void): () => void { const sub = Linking.addEventListener("url", ({ url }) => onUrl(url)); Linking.getInitialURL().then((url) => { if (url) onUrl(url); }); return () => sub.remove();}
// fragmento app.json - los dominios asociados son derechos nativos{ "expo": { "scheme": "myapp", "ios": { "associatedDomains": ["applinks:shop.example.com"] } }}
Los dominios asociados y los filtros de intents de Android se aplican durante el prebuild - exprésalos en la configuración, no ediciones manuales de Info.plist
Bluetooth web no está disponible en React Native. El escaneo de BLE, emparejamiento y lectura/escritura de GATT requieren CoreBluetooth (iOS) y BluetoothGatt (Android):
// Conceptual - las librerías BLE reales exponen módulos nativosimport { BleManager } from "react-native-ble-plx";const manager = new BleManager();export async function scanForPeripherals(): Promise<void> { manager.startDeviceScan(null, null, (error, device) => { if (error) return; if (device?.name) console.log("Found", device.name); });}
Las librerías BLE nunca están en Expo Go - planifica expo-dev-client desde el primer día
BLE en segundo plano en iOS requiere derechos adicionales y agota la batería - documenta en tu ADR
A veces la brecha no es capacidad sino cumplimiento: fijación de certificado, detección de jailbreak, SDKs de pago propios o APIs requeridas por MDM. Estos se envían como binarios nativos sin equivalente de Expo SDK:
// Fachada de capa de aplicación - la implementación es un módulo nativo o SDK de tercerosexport type AttestationResult = { passed: boolean; reason?: string };export async function attestDevice(): Promise<AttestationResult> { // Módulo nativo: DeviceCheck (iOS) / Play Integrity (Android) const NativeAttestation = require("../native/attestation").default; return NativeAttestation.runAsync();}
La revisión de tienda puede requerir versiones específicas de SDK nativos - los wrappers de JS deben coincidir con construcciones nativas fijadas
Cada solución alternativa solo de polyfill que toque la seguridad debe ser rechazada en revisión de código - ver Reglas de Módulos Nativos
import { requireNativeModule, requireOptionalNativeModule } from "expo-modules-core";// Lanza si falta clase nativa en binarioconst FileSystem = requireNativeModule("ExpoFileSystem");// Devuelve null en tests o cuando módulo no está enlazadoconst Optional = requireOptionalNativeModule("ExpoCustomFeature");
Esperar que Expo Go cargue cualquier paquete nativo npm - solo funcionan módulos preinstalados. Solución:npx expo install expo-dev-client y construir un cliente de desarrollo.
Instalar con npm install en lugar de npx expo install - las versiones de JS y binarios nativos se desvían. Solución: siempre npx expo install <package> para deps nativos.
Omitir reconstrucción nativa después del cambio de package.json - el recarga en caliente de Metro no compila Swift/Kotlin. Solución:npx expo run:ios / eas build después de cada cambio nativo.
Llamar a módulos nativos a nivel superior del módulo en tests - Jest no tiene bridge. Solución: mockear en límite de servicio o usar jest-expo.
Editar manualmente ios/ y android/ en aplicaciones CNG - el siguiente prebuild --clean borra ediciones. Solución: codifica cambios en plugins de configuración según CNG prebuild.
Un puente entre JavaScript y código de plataforma (Swift, Kotlin, Objective-C, Java, C++). En React Native 0.86 con Nueva Arquitectura, la mayoría de módulos usan JSI y TurboModules para llamadas síncronas y tipadas.
¿Necesito código nativo para cada paquete de Expo SDK?
No - los consumes desde JavaScript. El equipo de Expo mantiene la implementación nativa. Solo escribes código nativo cuando ningún SDK o paquete comunitario se ajusta.
¿Puedo enviar cambios de código nativo sobre OTA?
No. OTA (EAS Update) envía solo JavaScript y assets. Los cambios de dependencia nativa requieren un nuevo binario de tienda o build de desarrollo.
Expo Go vs development build - ¿regla rápida?
Expo Go: solo módulos SDK, pico más rápido. Development build: tus deps nativos de package.json, requerido para módulos de terceros y personalizados.
¿Cómo sé si un paquete npm tiene código nativo?
Busca ios/, android/, .podspec o expo-module.config.json en el paquete. Ejecuta npx expo-doctor después de instalar - marca deps nativos incompatibles.