React Native puro, Expo o Flutter: Elegir la tecnología móvil adecuada para tu equipo
Una comparación práctica de React Native puro, Expo y Flutter: cómo se ven en el código, dónde presentan limitaciones, y un marco de cinco preguntas para elegir entre ellos.
Elegir una plataforma móvil multiplataforma solía ser cuestión de determinar qué compromisos se estaban dispuestos a aceptar. Hoy en día, React Native, Expo y Flutter permiten desarrollar aplicaciones que parecen nativas, funcionan sin problemas y pueden llegar a audiencias muy grandes, por lo que la velocidad bruta rara vez es el factor decisivo. Lo que realmente importa es la idoneidad: las habilidades que ya tiene su equipo, el código con el que cuenta y la rapidez con la que necesita lanzar la aplicación en las tiendas. Este artículo trata a Expo como una opción por sí misma y no como algo secundario a React Native, explica cómo se comporta cada opción en la práctica y termina con un breve conjunto de preguntas que puede utilizar para elegir una.
¿Qué ha cambiado en las tres plataformas?
Varias debilidades estructurales que influenciaron las comparaciones anteriores ya han sido corregidas:
- La nueva arquitectura de React Native es la predeterminada. Se aleja del puente asíncrono heredado, lo que permite que JavaScript y el código nativo se comuniquen de manera más directa. Como resultado, las llamadas a los módulos nativos son considerablemente más económicas.
- El renderizador Impeller de Flutter reemplazó a Skia como opción predeterminada en dispositivos móviles. Impeller compila sus sombreadores con antelación, lo que elimina los problemas de rendimiento causados por la compilación de sombreadores en la primera ejecución y mantiene una tasa de fotogramas más constante.
- Expo se ha convertido en la forma estándar de desarrollar aplicaciones React Native. Ya no es un entorno de pruebas para principiantes, sino una cadena de herramientas para producción utilizada por grandes empresas.
Los resultados de pruebas publicados suelen mostrar que Flutter está ligeramente por delante en términos de rendimiento al renderizar interfaces con muchas animaciones, mientras que React Native lidera en tiempo de inicio, consumo de memoria e I/O nativo. Hay que tratar con cautela cualquier cifra específica, ya que varían mucho según la aplicación y el dispositivo. En la mayoría de los productos, esa diferencia ya no es determinante para el resultado final. Si desea conocer la perspectiva de los equipos que han trabajado con estas opciones durante tiempo, nuestro artículo sobre las decisiones entre Flutter y React Native que solo se manifiestan en producción aborda los aspectos a largo plazo.
React Native puro: máximo control, máxima responsabilidad
React Native permite escribir la interfaz en JavaScript o TypeScript con React, mientras que el framework renderiza componentes nativos reales en iOS y Android. No hay WebView ni canvas personalizado: un View se convierte en una vista nativa y un Text en un componente de texto nativo.
El contador a continuación muestra la forma básica de una aplicación con la Nueva Arquitectura y el renderizador Fabric. El estado se almacena en un gancho useState, el botón es un Pressable, y los estilos se declaran una sola vez con StyleSheet.create para que puedan validarse y reutilizarse.
// App.tsx — React Native (New Architecture, Fabric)
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function App() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.counter}>{count}</Text>
<Pressable style={styles.button} onPress={() => setCount(c => c + 1)}>
<Text style={styles.buttonText}>Tap me</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
counter: { fontSize: 48, fontWeight: '700', marginBottom: 24 },
button: { backgroundColor: '#2563eb', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 12 },
buttonText: { color: 'white', fontSize: 16, fontWeight: '600' },
});
Nada en este fragmento es específico de la Nueva Arquitectura; el mismo código del componente funciona en ambas. El cambio de arquitectura ocurre en el fondo, en la forma en que el renderizador y los módulos nativos se comunican con JavaScript.
Fortalezas
- Widgets nativos reales. Se obtienen los controles propios de la plataforma, el comportamiento de accesibilidad y el renderizado de texto, en lugar de una aproximación.
- El mayor pool de talento. Las habilidades en JavaScript, TypeScript y React son mucho más comunes que las de las otras opciones.
- Acceso total a lo nativo. Se poseen los directorios
ios/yandroid/, por lo que se puede utilizar Swift, Kotlin, Objective-C o Java cada vez que sea necesario. - Un ecosistema enorme. npm ofrece más paquetes de terceros que pub.dev, y la mayoría de los SDKs móviles para pagos, mapas y análisis incluyen envoltorios oficiales de React Native.
- Llamadas a lo nativo más rápidas. Fabric, TurboModules y JSI eliminan el antiguo cuello de botella del puente asíncrono, por lo que las llamadas al código nativo son casi inmediatas.
Debilidades
- Tú eres responsable de las herramientas de compilación nativas. Las incompatibilidades entre versiones de Xcode, Gradle y CocoaPods, así como los conflictos de dependencias nativas, siguen siendo una fuente frecuente de problemas.
- La consistencia multiplataforma requiere esfuerzo. Dado que el framework se adapta intencionadamente a widgets nativos, iOS y Android tendrán un aspecto diferente a menos que se diseñe para lograr consistencia.
- Tú debes encargarte de la infraestructura de distribución. Implementar CI/CD, firmado de código y actualizaciones sin conexión no es sencillo si se hace desde cero, y ese es precisamente el vacío que Expo cubre.
- Módulos de terceros de calidad variable. La calidad del mantenimiento de los módulos nativos varía desde excelente hasta completamente abandonado.
Consejos prácticos
- Evite crear una nueva aplicación con React Native puro a menos que tenga un motivo concreto, como un SDK nativo muy específico o una aplicación nativa existente que vaya a migrar poco a poco. Comience con Expo y genere proyectos nativos solo cuando los necesite.
- Utilice
react-native-reanimatedyreact-native-gesture-handlerpara interacciones sensibles al rendimiento. Estos componentes ejecutan animaciones y gestos en el hilo de la interfaz de usuario, por lo que un hilo de JavaScript ocupado no provoca pérdida de fotogramas. - Mantenga habilitado Hermes; es el motor por defecto. Compila JavaScript en bytecode con antelación, lo que reduce significativamente el tiempo de inicio y el uso de memoria en comparación con JavaScriptCore.
Expo: React Native con la infraestructura incluida
Expo es un framework y un conjunto de servicios desarrollados sobre React Native. Anteriormente tenía la reputación de ser un entorno restringido sin acceso al código nativo. Eso ya no es cierto: gracias a la preconstrucción, también conocida como generación nativa continua (CNG), Expo funciona con todo el ecosistema de módulos nativos.
La pantalla a continuación utiliza Expo Router, que asocia los archivos del directorio app/ con rutas de la misma manera que Next.js lo hace para el web. app/index.tsx es la ruta principal, y router.push('/profile') navega al archivo que define /profile. Los enlaces profundos y las URLs web siguen la misma estructura.
// app/index.tsx — Expo Router (file-based routing)
import { View, Text, Pressable } from 'react-native';
import { useRouter } from 'expo-router';
export default function HomeScreen() {
const router = useRouter();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16 }}>
<Text style={{ fontSize: 24, fontWeight: '600' }}>Welcome</Text>
<Pressable onPress={() => router.push('/profile')}>
<Text style={{ color: '#2563eb' }}>Go to Profile</Text>
</Pressable>
</View>
);
}
Crear y ejecutar un proyecto requiere tres comandos. El primero crea la estructura básica de la aplicación, y npx expo start inicia el servidor de desarrollo para que pueda abrir la aplicación en un dispositivo o simulador.
# Spin up a new project in under a minute
npx create-expo-app@latest my-app
cd my-app
npx expo start
Fortalezas
- No hay herramientas nativas para comenzar. Para la mayor parte del desarrollo no se necesita Xcode ni Android Studio; se pueden realizar pruebas en un dispositivo físico con Expo Go o un cliente de desarrollo personalizado.
- EAS Build. Las compilaciones para iOS y Android se ejecutan en la nube desde cualquier sistema operativo, por lo que es posible generar una compilación para iOS desde Windows o Linux.
- EAS Update. Las actualizaciones sin conexión entregan cambios en JavaScript y recursos a los usuarios de inmediato, sin esperar la revisión de la tienda, siempre y cuando no haya cambios en el código nativo.
- Expo Router. El enrutamiento basado en archivos con enlaces profundos y navegación universal para aplicaciones web y nativas está disponible desde el primer momento.
expo-camera, expo-notifications, expo-location y expo-image están integrados, documentados y alineados en versiones para que funcionen juntos.Info.plist y AndroidManifest.xml de forma declarativa desde app.json en lugar de editarlos a mano, lo que mantiene las compilaciones CI reproducibles.npx expo prebuild se generan las carpetas nativas cada vez que necesitas un control total sobre el código nativo.Debilidades
- Los SDKs especializados requieren trabajo adicional. Algunos SDKs nativos especializados siguen exigiendo que se escriba un plugin de configuración o módulo nativo propio, lo cual implica un poco más de trabajo que en React Native puro, aunque la brecha sigue disminuyendo.
- La trampa de Expo Go. Depender en gran medida de Expo Go puede ocultar el hecho de que un módulo nativo personalizado no funcionará hasta que se cree una compilación de desarrollo. Esto suele sorprender a los principiantes.
- Costos de servicio. EAS Build y Update ofrecen planes gratuitos, pero los equipos serios de producción suelen pasar a planes de pago, un costo que React Native puro con CI autohospedado evita.
- Límites heredados. Al ser una capa sobre React Native, Expo mantiene sus desventajas: divergencias en la interfaz de usuario según la plataforma y un hilo de JavaScript que puede convertirse en un cuello de botella bajo cargas computacionales elevadas.
Consejos prácticos
- Ejecuta
npx expo prebuildcuando necesites un módulo nativo que Expo no cubre. Crea las carpetasios/yandroid/según sea necesario, de modo que el código nativo esté siempre a mano. Nuestro artículo sobre tratar carpetas nativas como resultado de la construccion con Expo prebuild y CNG explica en profundidad este flujo de trabajo. - Utiliza EAS Update para correcciones urgentes, no para funcionalidades que modifiquen el comportamiento nativo. Las reglas de las tiendas de aplicaciones restringen qué cambios se pueden realizar en el código descargado, y publicar nuevas funcionalidades disfrazadas de actualización OTA conlleva el riesgo de que sean rechazadas; lee las políticas actuales de Apple y Google antes de confiar en esta opción.
- Adopta Expo Router al inicio de un proyecto nuevo. Adaptar el enrutamiento basado en archivos a una configuración de navegación existente es un proceso complicado.
expo doctor antes de cada compilación de lanzamiento. Detecta incoherencias en las dependencias y versiones que, de otro modo, se manifestarían como fallas crípticas en la compilación nativa.Flutter: control total de cada píxel
Flutter sigue un enfoque fundamentalmente diferente. En lugar de mapear elementos a widgets de la plataforma, dibuja toda la interfaz directamente con su motor Impeller, y el código Dart se compila por adelantado a código máquina nativo ARM o x86.
El contador de Dart que se muestra a continuación sigue el mismo patrón que el ejemplo de React Native. MyApp envuelve la aplicación en MaterialApp; CounterScreen es un StatefulWidget cuya clase de estado almacena _count, y al presionar el botón se llama a setState, lo que indica a Flutter que reconstruya ese subárbol con el nuevo valor.
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_count', style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Tap me'),
),
],
),
),
);
}
}
El flujo de trabajo en la línea de comandos abarca todo el ciclo de vida: crear el proyecto, ejecutarlo con recarga en tiempo real durante el desarrollo, y generar artefactos de lanzamiento para Google Play (un paquete de aplicación) y la App Store (un IPA).
flutter create my_app
cd my_app
flutter run # hot reload in under a second
flutter build appbundle --release # Android
flutter build ipa --release # iOS
Fortalezas
- Interfaz idéntica en todas partes. Dado que Flutter renderiza cada widget por sí mismo, el comportamiento y la apariencia son consistentes en iOS, Android, web y escritorio, sin problemas propios de cada plataforma.
- Rendimiento sólido en animaciones. Gracias a los shaders compilados con antelación y al acceso directo a la GPU a través de Impeller, Flutter suele liderar en comparaciones de tasa de fotogramas para interfaces complejas con muchas animaciones.
- Seis destinos a partir de una única base de código. iOS, Android, web, Windows, macOS y Linux se generan todos a partir del mismo código Dart.
Debilidades
- Dart tiene un ecosistema más pequeño. Es más difícil contratar desarrolladores especializados en Dart que en JavaScript o TypeScript, y incluso los desarrolladores experimentados suelen necesitar unas semanas para ser productivos.
- Puede parecer ligeramente no nativo. Dado que no utiliza widgets de la plataforma, los usuarios atentos pueden notar pequeñas diferencias en el comportamiento, aunque esto ha disminuido considerablemente.
- Binarios más grandes. El motor de Flutter se incluye en cada aplicación, por lo que los paquetes suelen ser más grandes que los de una aplicación React Native equivalente.
- Menos paquetes especializados. pub.dev es bueno, pero tiene menos opciones que npm, especialmente en lo que respecta a wrappers para SDKs nativos especializados.
- Sin reutilización de código web de React. Una organización con una base de código web en React ya establecida no puede reutilizar nada en el entorno web.
Consejos prácticos
- Habilite
flutter analyzecon reglas estrictas de lint desde el primer día. La seguridad contra valores nulos en Dart es una verdadera ventaja, pero solo si evita socavarla utilizando tiposdynamicen todas partes. - Elija un enfoque de gestión de estado como Riverpod o Bloc para proyectos que vayan más allá de un prototipo;
setStatepor sí solo no es suficiente para aplicaciones reales. - Consulte la vista de rendimiento de DevTools antes de concluir que existe un problema de lentitud. Impeller ya ha eliminado la mayor parte de las interrupciones causadas por la compilación de shaders en el pasado.
- Si lo que importa es la web, pruebe Flutter Web con antelación. Sus renderizadores se comportan de manera diferente a los de móvil, y el tamaño del paquete puede ser sorprendente. En el momento de escribir esto, Flutter está consolidándose en sus renderizadores basados en CanvasKit, así que consulte la documentación actual para saber qué opciones siguen estando soportadas.
Cinco preguntas que ayudan a decidir
Responda estas preguntas en orden. Generalmente, la primera pregunta con una respuesta clara decide todo.
- ¿Su equipo ya trabaja con React y JavaScript? Si es así, permanezca en el ecosistema de React Native y utilice Expo por defecto. Si no, y tiene libertad para elegir, tanto Flutter como Expo son opciones válidas; elija el lenguaje que su equipo prefiera aprender.
- ¿Necesita un diseño idéntico al pixel o animaciones personalizadas complejas, como en juegos, herramientas creativas o aplicaciones con mucha visualización? Flutter es la opción por defecto más adecuada.
- ¿Desea compartir código o componentes con una aplicación web existente de React? React Native, a través de React Native Web, tiene una ventaja real; Flutter debe comenzar desde cero en el entorno web.
- ¿Necesita implementar correcciones solo en JavaScript sin pasar por revisión en la tienda, o crear aplicaciones para iOS sin tener una Mac? La función EAS Update y EAS Build de Expo abordan ambos casos directamente.
- ¿Está integrando pantallas multiplataforma en una gran aplicación nativa existente? React Native puro, o la opción de integración en aplicaciones de Flutter, son más adecuados que un proyecto nuevo con Expo.
Recomendaciones
- Para un equipo de React o JavaScript que inicia una nueva aplicación, elija Expo. Elimina la mayoría de los problemas históricos de React Native relacionados con las herramientas nativas, CI/CD y actualizaciones OTA, al tiempo que mantiene toda la potencia nativa disponible cuando se necesita.
- Elija Flutter cuando la calidad de la interfaz, el rendimiento de las animaciones y la accesibilidad en móviles, escritorio y web sean más importantes que reutilizar el ecosistema de JavaScript, o cuando su equipo no tenga una preferencia clara por un lenguaje y esté comenzando desde cero.
- Reserve React Native puro para situaciones en las que es necesario gestionar directamente los proyectos nativos, típicamente cuando existe una gran base de código nativo ya existente o se requieren integraciones nativas inusuales.
Conclusión
Los tres frameworks han alcanzado un nivel suficiente de rendimiento y madurez, por lo que las capacidades del framework rara vez son el cuello de botella. Los factores decisivos son las personas de tu equipo, el código que ya tienes y la rapidez con la que necesitas lanzar la aplicación. Considera a Expo como la forma predeterminada de usar React Native, reserva React Native puro para casos en los que lo importante es gestionar los proyectos nativos directamente, y elige Flutter cuando el control en la renderización y el alcance multiplataforma superen los beneficios de seguir utilizando JavaScript. Sea cual sea tu elección, valida la parte más arriesgada de tu aplicación, ya sea un SDK nativo, una animación compleja o la versión para web, durante las primeras semanas y no solo en el momento del lanzamiento.