Typescript
react native
Expo Router
Navegación Avanzada
UX Móvil
Estos patrones son los que separan una app funcional de una app profesional. Y aunque pueden parecer intimidantes al principio, una vez que comprendes la lógica detrás de cada uno, se vuelven herramientas poderosas para construir experiencias sofisticadas.
1app/ 2 ├─ (tabs)/ 3 │ ├─ _layout.tsx → Tab Navigator 4 │ ├─ index.tsx → Pestaña "Inicio" 5 │ ├─ cart.tsx → Pestaña "Carrito" 6 │ └─ profile/ 7 │ ├─ _layout.tsx → Stack Navigator interno 8 │ ├─ index.tsx → Pantalla principal del perfil 9 │ ├─ edit.tsx → Editar perfil 10 │ └─ history.tsx → Historial de pedidos 11 └─ _layout.tsx → Root layout
En este caso, el tab "Perfil" no es solo una pantalla: es un stack completo. Cuando el usuario entra a su perfil, puede navegar hacia editar datos o ver su historial, y al volver atrás, regresa a la pantalla principal del perfil, no a la pestaña anterior.
1// app/(tabs)/_layout.tsx 2import { Tabs } from "expo-router"; 3import { Home, ShoppingCart, User } from "lucide-react-native"; 4 5export default function TabsLayout() { 6 return ( 7 <Tabs> 8 <Tabs.Screen 9 name="index" 10 options={{ 11 title: "Inicio", 12 tabBarIcon: ({ color }) => <Home color={color} size={24} /> 13 }} 14 /> 15 <Tabs.Screen 16 name="cart" 17 options={{ 18 title: "Carrito", 19 tabBarIcon: ({ color }) => <ShoppingCart color={color} size={24} /> 20 }} 21 /> 22 <Tabs.Screen 23 name="profile" 24 options={{ 25 title: "Perfil", 26 tabBarIcon: ({ color }) => <User color={color} size={24} /> 27 }} 28 /> 29 </Tabs> 30 ); 31}
Stack interno del perfil:
1// app/(tabs)/profile/_layout.tsx 2import { Stack } from "expo-router"; 3 4export default function ProfileLayout() { 5 return ( 6 <Stack> 7 <Stack.Screen 8 name="index" 9 options={{ headerShown: false }} // El tab ya muestra header 10 /> 11 <Stack.Screen 12 name="edit" 13 options={{ title: "Editar perfil" }} 14 /> 15 <Stack.Screen 16 name="history" 17 options={{ title: "Historial de pedidos" }} 18 /> 19 </Stack> 20 ); 21}
Ahora, desde la pantalla principal del perfil, puedes navegar así:
1// app/(tabs)/profile/index.tsx 2import { Link } from "expo-router"; 3 4export default function ProfileScreen() { 5 return ( 6 <View> 7 <Text>Bienvenido a tu perfil</Text> 8 <Link href="/profile/edit">Editar datos</Link> 9 <Link href="/profile/history">Ver historial</Link> 10 </View> 11 ); 12}
El header de una pantalla no es solo estético: comunica contexto, ofrece acciones rápidas y refuerza la identidad de la app. Por defecto, Expo Router te da un header básico, pero las apps profesionales necesitan más control.
Puedes modificar colores, fuentes y elementos del header desde las opciones de cada pantalla:
1<Stack.Screen 2 name="details" 3 options={{ 4 title: "Detalle del producto", 5 headerStyle: { backgroundColor: "#6200ee" }, 6 headerTintColor: "#fff", 7 headerTitleStyle: { fontWeight: "bold" } 8 }} 9/>
Esto funciona para casos simples. Pero, ¿qué pasa cuando necesitas un header completamente personalizado?
1<Stack.Screen 2 name="product" 3 options={{ 4 headerTitle: () => <CustomHeader />, 5 headerRight: () => ( 6 <TouchableOpacity onPress={() => console.log("Compartir")}> 7 <Share2 color="#fff" size={24} /> 8 </TouchableOpacity> 9 ) 10 }} 11/>
Puedes insertar cualquier componente React en el header. Esto abre posibilidades infinitas: buscadores en el header, botones de filtro, indicadores de carga, animaciones.
Un patrón muy común en apps modernas es el header que se oculta al hacer scroll hacia abajo y reaparece al subir. Esto maximiza el espacio de contenido sin sacrificar la navegación.
1<Stack.Screen 2 name="feed" 3 options={{ 4 headerTransparent: true, 5 headerBlurEffect: "light" 6 }} 7/>
O mejor aún, usar Animated
para crear transiciones suaves basadas en el scroll del usuario. Aunque esto requiere un poco más de código, el resultado es una experiencia fluida y profesional.
El deep linking permite que usuarios abran tu app en una pantalla específica desde un enlace externo: un email, una notificación push, un código QR. Es fundamental para marketing, retención y experiencia de usuario.
Con Expo Router, el deep linking funciona automáticamente gracias a la estructura basada en archivos. Solo necesitas configurar el esquema de URL:
1// app.json 2{ 3 "expo": { 4 "scheme": "myapp" 5 } 6}
Ahora, un enlace como myapp://profile/42
abrirá la pantalla de perfil con userId: 42
.
Para URLs reales (https://example.com/profile/42
), necesitas configurar universal links y app links. Esto requiere archivos de verificación en tu dominio, pero Expo simplifica el proceso con EAS:
1{ 2 "expo": { 3 "ios": { 4 "associatedDomains": ["applinks:example.com"] 5 }, 6 "android": { 7 "intentFilters": [ 8 { 9 "action": "VIEW", 10 "data": { "scheme": "https", "host": "example.com" } 11 } 12 ] 13 } 14 } 15}
Ahora, cuando alguien haga clic en un enlace de tu dominio, el sistema operativo preguntará si quiere abrir la app. Si el usuario acepta, la app se abre directamente en la pantalla correcta.
1<Stack.Screen 2 name="details" 3 options={{ 4 animation: "fade", 5 // o "slide_from_bottom", "slide_from_right", "flip", etc. 6 }} 7/>
Para animaciones más complejas, puedes usar @react-navigation/stack
con gesture handlers y reanimated:
1cardStyleInterpolator: ({ current, layouts }) => ({ 2 cardStyle: { 3 transform: [ 4 { 5 translateX: current.progress.interpolate({ 6 inputRange: [0, 1], 7 outputRange: [layouts.screen.width, 0], 8 }), 9 }, 10 ], 11 }, 12})
Esto requiere entender interpolación y animaciones, pero el resultado es control total sobre cómo se mueven las pantallas. Algunas apps premium usan transiciones customizadas para reforzar su identidad visual.