Top 30 preguntas de entrevista React Native: guía completa 2026
Las 30 preguntas más frecuentes en entrevistas de React Native. Respuestas detalladas con ejemplos de código para conseguir tu puesto de desarrollador móvil.

Las entrevistas técnicas de React Native evalúan habilidades de desarrollo móvil multiplataforma, particularidades de iOS/Android y patrones de rendimiento. Esta guía recorre las 30 preguntas más frecuentes, con respuestas detalladas y ejemplos de código para una preparación efectiva.
Estas preguntas abarcan desde fundamentos hasta conceptos avanzados. Dominar la arquitectura de React Native y comprender las diferencias con React web resulta esencial para superar la entrevista.
Fundamentos de React Native
1. ¿Cuál es la diferencia entre React y React Native?
React es una librería para construir interfaces web, mientras que React Native habilita el desarrollo de aplicaciones móviles nativas para iOS y Android.
La diferencia fundamental radica en el renderizado: React utiliza un Virtual DOM que se traduce a elementos HTML, mientras que React Native emplea un puente que se comunica con componentes nativos en cada plataforma.
// React (Web) - uses HTML elements
function WebComponent() {
return (
<div className="container">
<span>Web text</span>
<button onClick={handleClick}>Click</button>
</div>
)
}
// React Native - uses native components
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
function NativeComponent() {
return (
<View style={styles.container}>
<Text>Native text</Text>
<TouchableOpacity onPress={handlePress}>
<Text>Press</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16
}
})Los componentes de React Native se traducen a UIView en iOS y a android.view en Android, ofreciendo rendimiento nativo.
2. ¿Cómo funciona la arquitectura de React Native?
React Native utiliza una arquitectura de tres capas: JavaScript, Bridge (o JSI en la nueva arquitectura) y Native.
El código JavaScript se ejecuta dentro de un motor JS (Hermes o JavaScriptCore). Las comunicaciones con el código nativo pasan por serialización JSON en la arquitectura antigua, o a través de JSI (JavaScript Interface) en la nueva arquitectura.
// Old architecture: asynchronous communication via Bridge
// The Bridge serializes messages as JSON between JS and Native
// New architecture (Fabric + TurboModules)
// JSI enables synchronous direct calls to native modules
// Example TurboModule usage
import { TurboModuleRegistry } from 'react-native'
// Synchronous access to native module
const DeviceInfo = TurboModuleRegistry.get('DeviceInfo')
const deviceName = DeviceInfo.getDeviceName() // Synchronous call
// With Fabric, rendering is smoother
// Components can be created synchronously
// Reducing jank during animationsLa nueva arquitectura mejora notablemente el rendimiento al eliminar la serialización JSON y permitir llamadas síncronas.
3. ¿Qué es el bundler Metro?
Metro es el bundler de JavaScript que utiliza React Native. Transforma el código fuente en un bundle optimizado para la ejecución móvil.
Metro se encarga de la resolución de módulos, la transformación de código (vía Babel) y el hot reloading durante el desarrollo.
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
// Custom configuration
config.resolver.assetExts.push('db') // Add extensions
config.resolver.sourceExts.push('cjs') // CommonJS support
// Transformer configuration
config.transformer.babelTransformerPath = require.resolve(
'react-native-svg-transformer'
)
// Production optimizations
config.transformer.minifierConfig = {
keep_classnames: true,
keep_fnames: true,
mangle: {
keep_classnames: true,
keep_fnames: true
}
}
module.exports = configMetro soporta Fast Refresh, lo que permite aplicar cambios al instante sin perder el estado de la aplicación.
4. Explica StyleSheet.create y sus ventajas
StyleSheet.create optimiza los estilos validándolos y convirtiéndolos a referencias numéricas, reduciendo el coste del puente.
// ❌ Inline styles - recreated on every render
function BadExample() {
return (
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
</View>
)
}
// ✅ StyleSheet.create - optimized and validated
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff'
},
title: {
fontSize: 18,
fontWeight: 'bold'
},
// Style composition
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 8
}
})
function GoodExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>Title</Text>
{/* Style combination */}
<View style={[styles.row, { marginTop: 10 }]}>
<Text>Content</Text>
</View>
</View>
)
}
// StyleSheet.absoluteFillObject for absolute positioning
const overlayStyles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)'
}
})5. ¿Cuál es la diferencia entre Flexbox web y React Native?
React Native utiliza Flexbox pero con valores por defecto distintos a la web, adaptados a interfaces móviles verticales.
// Key differences from web
const styles = StyleSheet.create({
container: {
// flexDirection: 'column' by default (vs 'row' on web)
// alignItems: 'stretch' by default
flex: 1
},
// React Native Flexbox
row: {
flexDirection: 'row', // Horizontal
justifyContent: 'space-between', // Main axis
alignItems: 'center', // Cross axis
flexWrap: 'wrap', // Line wrapping
gap: 8 // Supported since RN 0.71
},
// Flex grow/shrink
flexItem: {
flex: 1, // Equivalent to flex: 1 1 0
flexGrow: 1, // Grow to fill
flexShrink: 0, // Don't shrink
flexBasis: 100 // Base size
},
// Absolute positioning
absolute: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
})
// Practical example: card with image and content
function Card() {
return (
<View style={cardStyles.container}>
<Image source={{ uri: imageUrl }} style={cardStyles.image} />
<View style={cardStyles.content}>
<Text style={cardStyles.title}>Title</Text>
<Text style={cardStyles.description}>Description</Text>
</View>
</View>
)
}
const cardStyles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: '#fff',
borderRadius: 8,
overflow: 'hidden'
},
image: {
width: 100,
height: 100
},
content: {
flex: 1, // Takes remaining space
padding: 12,
justifyContent: 'center'
},
title: {
fontSize: 16,
fontWeight: '600'
},
description: {
fontSize: 14,
color: '#666'
}
})Navegación y componentes
6. ¿Cómo se implementa la navegación con React Navigation?
React Navigation es la solución estándar para navegar en React Native. Ofrece distintos tipos de navegadores adaptados a los patrones móviles.
// Installing dependencies
// npm install @react-navigation/native @react-navigation/native-stack
// npm install react-native-screens react-native-safe-area-context
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
// TypeScript typing for navigation params
type RootStackParamList = {
Home: undefined
Profile: { userId: string }
Settings: { section?: string }
}
const Stack = createNativeStackNavigator<RootStackParamList>()
const Tab = createBottomTabNavigator()
// Tab navigation
function TabNavigator() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
// Dynamic icon based on tab
const iconName = route.name === 'Home' ? 'home' : 'settings'
return <Icon name={iconName} size={size} color={color} />
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray'
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
)
}
// Stack navigation
function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
animation: 'slide_from_right' // Native animation
}}
>
<Stack.Screen
name="Home"
component={TabNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({
title: `Profile ${route.params.userId}`
})}
/>
</Stack.Navigator>
</NavigationContainer>
)
}7. ¿Cómo se gestionan listas de alto rendimiento con FlatList?
FlatList está optimizada para listas extensas mediante virtualización automática, renderizando únicamente los elementos visibles.
import { FlatList, RefreshControl } from 'react-native'
function ProductList() {
const [products, setProducts] = useState([])
const [refreshing, setRefreshing] = useState(false)
const [loading, setLoading] = useState(false)
// Initial loading
const fetchProducts = async (page = 1) => {
const response = await api.getProducts(page)
return response.data
}
// Pull-to-refresh
const onRefresh = useCallback(async () => {
setRefreshing(true)
const data = await fetchProducts(1)
setProducts(data)
setRefreshing(false)
}, [])
// Infinite pagination
const loadMore = useCallback(async () => {
if (loading) return
setLoading(true)
const nextPage = Math.ceil(products.length / 20) + 1
const data = await fetchProducts(nextPage)
setProducts(prev => [...prev, ...data])
setLoading(false)
}, [products.length, loading])
// Item rendering
const renderItem = useCallback(({ item }) => (
<ProductCard product={item} />
), [])
// Key extraction
const keyExtractor = useCallback((item) => item.id.toString(), [])
// Item separator
const ItemSeparator = useCallback(() => (
<View style={{ height: 12 }} />
), [])
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
ItemSeparatorComponent={ItemSeparator}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
windowSize={5}
// Pull-to-refresh
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#007AFF"
/>
}
// Infinite pagination
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <ActivityIndicator /> : null}
// Empty list
ListEmptyComponent={<EmptyState message="No products" />}
/>
)
}Conviene memoizar siempre renderItem con useCallback y extraer los componentes pesados. Hay que evitar funciones inline en renderItem que provoquen renders innecesarios.
8. ¿Cuál es la diferencia entre TouchableOpacity, Pressable y TouchableHighlight?
Estos componentes gestionan interacciones táctiles con distintos tipos de feedback visual.
import {
TouchableOpacity,
TouchableHighlight,
Pressable,
StyleSheet
} from 'react-native'
function InteractionExamples() {
return (
<View style={styles.container}>
{/* TouchableOpacity: reduces opacity on touch */}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => console.log('Pressed')}
style={styles.button}
>
<Text>TouchableOpacity</Text>
</TouchableOpacity>
{/* TouchableHighlight: adds background color */}
<TouchableHighlight
underlayColor="#ddd"
onPress={() => console.log('Pressed')}
style={styles.button}
>
<Text>TouchableHighlight</Text>
</TouchableHighlight>
{/* Pressable: modern API with more control */}
<Pressable
onPress={() => console.log('Pressed')}
onLongPress={() => console.log('Long press')}
delayLongPress={500}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
>
{({ pressed }) => (
<Text style={pressed && styles.textPressed}>
{pressed ? 'Pressed!' : 'Pressable'}
</Text>
)}
</Pressable>
{/* Pressable with hitSlop to enlarge touch area */}
<Pressable
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
onPress={() => console.log('Pressed')}
style={styles.smallButton}
>
<Text>Small button</Text>
</Pressable>
</View>
)
}
const styles = StyleSheet.create({
container: {
gap: 16,
padding: 20
},
button: {
backgroundColor: '#007AFF',
padding: 16,
borderRadius: 8,
alignItems: 'center'
},
buttonPressed: {
backgroundColor: '#0056b3',
transform: [{ scale: 0.98 }]
},
textPressed: {
color: '#fff'
},
smallButton: {
padding: 8,
backgroundColor: '#eee'
}
})Pressable se recomienda para proyectos nuevos, ya que ofrece mayor control y una API más coherente.
9. ¿Cómo se crean animaciones fluidas?
React Native ofrece varias APIs de animación: Animated (integrada) y Reanimated (más performante).
import { Animated, Easing } from 'react-native'
import Reanimated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming
} from 'react-native-reanimated'
// Animation with Animated (native API)
function FadeInView({ children }) {
const fadeAnim = useRef(new Animated.Value(0)).current
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
easing: Easing.ease,
useNativeDriver: true // Performant on UI thread
}).start()
}, [])
return (
<Animated.View style={{ opacity: fadeAnim }}>
{children}
</Animated.View>
)
}
// Animation with Reanimated (recommended for complex animations)
function BouncyButton() {
const scale = useSharedValue(1)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }]
}))
const handlePressIn = () => {
scale.value = withSpring(0.95, {
damping: 10,
stiffness: 400
})
}
const handlePressOut = () => {
scale.value = withSpring(1, {
damping: 10,
stiffness: 400
})
}
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Reanimated.View style={[styles.button, animatedStyle]}>
<Text style={styles.buttonText}>Press</Text>
</Reanimated.View>
</Pressable>
)
}
// List animation with LayoutAnimation
import { LayoutAnimation, UIManager, Platform } from 'react-native'
// Enable on Android
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental?.(true)
}
function AnimatedList() {
const [items, setItems] = useState([])
const addItem = () => {
// Configure animation before state change
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring)
setItems(prev => [...prev, { id: Date.now() }])
}
const removeItem = (id) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setItems(prev => prev.filter(item => item.id !== id))
}
return (
<View>
<Button title="Add" onPress={addItem} />
{items.map(item => (
<TouchableOpacity key={item.id} onPress={() => removeItem(item.id)}>
<View style={styles.item}>
<Text>Item {item.id}</Text>
</View>
</TouchableOpacity>
))}
</View>
)
}¿Listo para aprobar tus entrevistas de React Native?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Estado y gestión de datos
10. ¿Cómo se gestiona el estado global en React Native?
Aplican las mismas soluciones que en React web: Context API, Redux, Zustand o MobX.
// Lightweight solution with Zustand
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'
// Store with persistence
const useAuthStore = create(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (email, password) => {
const response = await api.login(email, password)
set({
user: response.user,
token: response.token,
isAuthenticated: true
})
},
logout: () => {
set({ user: null, token: null, isAuthenticated: false })
},
updateProfile: (updates) => {
set(state => ({
user: { ...state.user, ...updates }
}))
}
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage)
}
)
)
// Usage in a component
function ProfileScreen() {
const { user, logout, updateProfile } = useAuthStore()
if (!user) return <LoginPrompt />
return (
<View style={styles.container}>
<Text style={styles.name}>{user.name}</Text>
<Text style={styles.email}>{user.email}</Text>
<TouchableOpacity onPress={logout} style={styles.logoutButton}>
<Text>Logout</Text>
</TouchableOpacity>
</View>
)
}
// Cart store
const useCartStore = create((set, get) => ({
items: [],
addItem: (product) => set(state => {
const existing = state.items.find(i => i.id === product.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === product.id
? { ...i, quantity: i.quantity + 1 }
: i
)
}
}
return { items: [...state.items, { ...product, quantity: 1 }] }
}),
removeItem: (id) => set(state => ({
items: state.items.filter(i => i.id !== id)
})),
getTotal: () => {
return get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
},
clearCart: () => set({ items: [] })
}))11. ¿Cómo se realizan llamadas a la API con gestión de caché?
React Query (TanStack Query) es la solución recomendada para gestionar datos del servidor.
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'
// Client configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
refetchOnWindowFocus: false // Mobile doesn't have "window focus"
}
}
})
// Provider in App.tsx
function App() {
return (
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</QueryClientProvider>
)
}
// Custom hook for products
function useProducts(categoryId) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: async () => {
const response = await fetch(`/api/products?category=${categoryId}`)
if (!response.ok) throw new Error('Network error')
return response.json()
},
enabled: !!categoryId // Don't execute if no categoryId
})
}
// Mutation with cache invalidation
function useAddToCart() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (product) => {
const response = await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify(product)
})
return response.json()
},
onSuccess: () => {
// Invalidate cart cache to force refetch
queryClient.invalidateQueries({ queryKey: ['cart'] })
},
onError: (error) => {
Alert.alert('Error', error.message)
}
})
}
// Usage in a component
function ProductListScreen({ categoryId }) {
const { data: products, isLoading, error, refetch } = useProducts(categoryId)
const addToCart = useAddToCart()
if (isLoading) return <LoadingSpinner />
if (error) return <ErrorView error={error} onRetry={refetch} />
return (
<FlatList
data={products}
renderItem={({ item }) => (
<ProductCard
product={item}
onAddToCart={() => addToCart.mutate(item)}
isAddingToCart={addToCart.isPending}
/>
)}
refreshing={isLoading}
onRefresh={refetch}
/>
)
}12. ¿Cómo se almacenan datos localmente?
Existen varias opciones según el tipo de dato: AsyncStorage para datos simples, MMKV para rendimiento y SQLite para datos estructurados.
// AsyncStorage - simple but slow for large volumes
import AsyncStorage from '@react-native-async-storage/async-storage'
const storage = {
async get(key) {
const value = await AsyncStorage.getItem(key)
return value ? JSON.parse(value) : null
},
async set(key, value) {
await AsyncStorage.setItem(key, JSON.stringify(value))
},
async remove(key) {
await AsyncStorage.removeItem(key)
},
async clear() {
await AsyncStorage.clear()
}
}
// MMKV - very performant (recommended)
import { MMKV } from 'react-native-mmkv'
const mmkv = new MMKV()
const fastStorage = {
get(key) {
const value = mmkv.getString(key)
return value ? JSON.parse(value) : null
},
set(key, value) {
mmkv.set(key, JSON.stringify(value))
},
remove(key) {
mmkv.delete(key)
},
// Primitive type support
getNumber(key) {
return mmkv.getNumber(key)
},
setNumber(key, value) {
mmkv.set(key, value)
},
getBoolean(key) {
return mmkv.getBoolean(key)
}
}
// Hook to use MMKV with React
function useMMKVStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
const stored = fastStorage.get(key)
return stored ?? defaultValue
})
const setStoredValue = useCallback((newValue) => {
setValue(newValue)
fastStorage.set(key, newValue)
}, [key])
return [value, setStoredValue]
}
// Usage
function SettingsScreen() {
const [theme, setTheme] = useMMKVStorage('theme', 'light')
const [notifications, setNotifications] = useMMKVStorage('notifications', true)
return (
<View>
<Switch
value={theme === 'dark'}
onValueChange={(v) => setTheme(v ? 'dark' : 'light')}
/>
<Switch
value={notifications}
onValueChange={setNotifications}
/>
</View>
)
}Para tokens y datos sensibles conviene usar expo-secure-store o react-native-keychain, que cifran la información mediante Keychain (iOS) y Keystore (Android).
Rendimiento y optimización
13. ¿Cómo se optimiza el rendimiento de una app React Native?
La optimización abarca varios aspectos: renderizado, memoria e interacciones.
const ProductCard = React.memo(function ProductCard({ product, onPress }) {
return (
<TouchableOpacity onPress={() => onPress(product.id)}>
<View style={styles.card}>
<Image source={{ uri: product.image }} style={styles.image} />
<Text style={styles.title}>{product.name}</Text>
<Text style={styles.price}>{product.price}$</Text>
</View>
</TouchableOpacity>
)
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.product.id === nextProps.product.id
})
// 2. Memoize callbacks
function ProductList({ products }) {
// ❌ New function on every render
// onPress={(id) => handlePress(id)}
// ✅ Stable function
const handlePress = useCallback((id) => {
navigation.navigate('Product', { id })
}, [navigation])
return (
<FlatList
data={products}
renderItem={({ item }) => (
<ProductCard product={item} onPress={handlePress} />
)}
/>
)
}
// 3. Optimize images
import FastImage from 'react-native-fast-image'
function OptimizedImage({ uri }) {
return (
<FastImage
source={{
uri,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable
}}
style={styles.image}
resizeMode={FastImage.resizeMode.cover}
/>
)
}
// 4. Use InteractionManager for heavy tasks
import { InteractionManager } from 'react-native'
function HeavyScreen() {
const [data, setData] = useState(null)
useEffect(() => {
// Wait for animations to complete
const task = InteractionManager.runAfterInteractions(() => {
const result = performHeavyComputation()
setData(result)
})
return () => task.cancel()
}, [])
return data ? <DataView data={data} /> : <LoadingView />
}
// 5. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
function Navigator() {
return (
<Stack.Navigator>
<Stack.Screen
name="Heavy"
component={HeavyScreen}
options={{ lazy: true }}
/>
</Stack.Navigator>
)
}14. ¿Cómo se depuran problemas de rendimiento?
React Native ofrece diversas herramientas para identificar cuellos de botella.
// Configuration in android/app/build.gradle and ios/Podfile
// See: https://fbflipper.com/
// 2. Console.time to measure operations
function fetchData() {
console.time('fetchData')
const data = await api.getData()
console.timeEnd('fetchData') // fetchData: 234ms
return data
}
// 3. Performance monitor (shake device → Show Perf Monitor)
// Shows JS and UI FPS
// 4. Hermes profiler for CPU
// Enable in metro.config.js
module.exports = {
transformer: {
hermesParser: true
}
}
// 5. Identify re-renders with why-did-you-render
// Installation: npm install @welldone-software/why-did-you-render
import React from 'react'
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React, {
trackAllPureComponents: true
})
}
// Mark a component to monitor
ProductCard.whyDidYouRender = true
// 6. Measure mount time
function useComponentTiming(componentName) {
const mountTime = useRef(Date.now())
useEffect(() => {
const duration = Date.now() - mountTime.current
console.log(`${componentName} mounted in ${duration}ms`)
return () => {
console.log(`${componentName} unmounted`)
}
}, [componentName])
}
// Usage
function MyComponent() {
useComponentTiming('MyComponent')
// ...
}15. ¿Cómo se gestiona el modo offline?
La gestión offline requiere una estrategia de caché y sincronización.
import NetInfo from '@react-native-community/netinfo'
// Hook to monitor connectivity
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true)
const [connectionType, setConnectionType] = useState(null)
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected)
setConnectionType(state.type)
})
return () => unsubscribe()
}, [])
return { isConnected, connectionType }
}
// Service with offline queue
class OfflineQueue {
constructor() {
this.queue = []
this.isProcessing = false
}
async add(action) {
this.queue.push({
id: Date.now(),
action,
timestamp: new Date().toISOString()
})
await this.persist()
}
async persist() {
await AsyncStorage.setItem('offline_queue', JSON.stringify(this.queue))
}
async load() {
const data = await AsyncStorage.getItem('offline_queue')
this.queue = data ? JSON.parse(data) : []
}
async process() {
if (this.isProcessing || this.queue.length === 0) return
this.isProcessing = true
const { isConnected } = await NetInfo.fetch()
if (!isConnected) {
this.isProcessing = false
return
}
while (this.queue.length > 0) {
const item = this.queue[0]
try {
await this.executeAction(item.action)
this.queue.shift()
await this.persist()
} catch (error) {
console.error('Failed to process action:', error)
break
}
}
this.isProcessing = false
}
async executeAction(action) {
switch (action.type) {
case 'CREATE_ORDER':
return api.createOrder(action.payload)
case 'UPDATE_PROFILE':
return api.updateProfile(action.payload)
default:
throw new Error(`Unknown action: ${action.type}`)
}
}
}
const offlineQueue = new OfflineQueue()
// Network status banner component
function NetworkBanner() {
const { isConnected } = useNetworkStatus()
if (isConnected) return null
return (
<View style={styles.banner}>
<Text style={styles.bannerText}>
Offline mode - Changes will be synchronized
</Text>
</View>
)
}Particularidades móviles
16. ¿Cómo se gestionan los permisos en iOS y Android?
Los permisos se manejan de forma distinta en cada plataforma. Librerías como react-native-permissions unifican la API.
import { Platform, Alert, Linking } from 'react-native'
import {
check,
request,
PERMISSIONS,
RESULTS,
openSettings
} from 'react-native-permissions'
// Permission configuration per platform
const PERMISSION_TYPES = {
camera: Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA
}),
photos: Platform.select({
ios: PERMISSIONS.IOS.PHOTO_LIBRARY,
android: PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
}),
location: Platform.select({
ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
})
}
// Hook to manage permissions
function usePermission(type) {
const [status, setStatus] = useState(RESULTS.UNAVAILABLE)
const permission = PERMISSION_TYPES[type]
useEffect(() => {
check(permission).then(setStatus)
}, [permission])
const requestPermission = useCallback(async () => {
const result = await request(permission)
setStatus(result)
if (result === RESULTS.BLOCKED) {
Alert.alert(
'Permission required',
`This feature requires ${type} access. Do you want to open settings?`,
[
{ text: 'No', style: 'cancel' },
{ text: 'Open', onPress: openSettings }
]
)
}
return result
}, [permission, type])
return {
status,
isGranted: status === RESULTS.GRANTED,
isDenied: status === RESULTS.DENIED,
isBlocked: status === RESULTS.BLOCKED,
requestPermission
}
}
// Usage
function CameraButton() {
const { isGranted, requestPermission } = usePermission('camera')
const handlePress = async () => {
if (!isGranted) {
const result = await requestPermission()
if (result !== RESULTS.GRANTED) return
}
// Open camera
navigation.navigate('Camera')
}
return (
<TouchableOpacity onPress={handlePress}>
<Text>Take a photo</Text>
</TouchableOpacity>
)
}17. ¿Cómo se implementan las notificaciones push?
Las notificaciones push requieren configuración nativa y un backend que las envíe.
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance } from '@notifee/react-native'
// Notification service
class NotificationService {
async initialize() {
// Request permission (iOS)
const authStatus = await messaging().requestPermission()
const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED
if (enabled) {
// Get FCM token
const token = await messaging().getToken()
await this.registerToken(token)
// Listen for token changes
messaging().onTokenRefresh(this.registerToken)
// Create Android channel
await notifee.createChannel({
id: 'default',
name: 'Notifications',
importance: AndroidImportance.HIGH
})
}
return enabled
}
async registerToken(token) {
// Send token to backend
await api.registerPushToken(token)
}
// Setup handlers
setupHandlers() {
// Notification received in foreground
messaging().onMessage(async (remoteMessage) => {
await this.displayNotification(remoteMessage)
})
// Notification tapped (app in background)
messaging().onNotificationOpenedApp((remoteMessage) => {
this.handleNotificationPress(remoteMessage)
})
// App opened from notification (app closed)
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
this.handleNotificationPress(remoteMessage)
}
})
}
async displayNotification(remoteMessage) {
const { title, body } = remoteMessage.notification
await notifee.displayNotification({
title,
body,
android: {
channelId: 'default',
pressAction: { id: 'default' }
},
data: remoteMessage.data
})
}
handleNotificationPress(remoteMessage) {
const { type, id } = remoteMessage.data
switch (type) {
case 'order':
navigation.navigate('OrderDetail', { orderId: id })
break
case 'message':
navigation.navigate('Chat', { conversationId: id })
break
}
}
}
// Usage in App.tsx
function App() {
useEffect(() => {
const notificationService = new NotificationService()
notificationService.initialize()
notificationService.setupHandlers()
}, [])
return <AppNavigator />
}18. ¿Cómo se manejan los deep links?
Los deep links permiten abrir la aplicación en una pantalla concreta desde una URL externa.
import { Linking } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
// Deep link configuration
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: 'home',
Product: {
path: 'product/:id',
parse: {
id: (id) => parseInt(id, 10)
}
},
Profile: 'profile/:userId?',
Settings: {
path: 'settings',
screens: {
Notifications: 'notifications',
Privacy: 'privacy'
}
}
}
},
// Custom function to get initial URL
async getInitialURL() {
// Check if app was opened via deep link
const url = await Linking.getInitialURL()
if (url) return url
// Check notifications
const message = await messaging().getInitialNotification()
if (message?.data?.link) return message.data.link
return null
},
// Subscribe to incoming links
subscribe(listener) {
// Standard deep links
const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
listener(url)
})
// Links from notifications
const unsubscribeNotification = messaging().onNotificationOpenedApp(
(message) => {
const link = message.data?.link
if (link) listener(link)
}
)
return () => {
linkingSubscription.remove()
unsubscribeNotification()
}
}
}
// Usage
function App() {
return (
<NavigationContainer linking={linking} fallback={<LoadingScreen />}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
// Testing deep links
// myapp://product/123
// https://myapp.com/profile/user456¿Listo para aprobar tus entrevistas de React Native?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Código nativo y módulos
19. ¿Cuándo y cómo se escribe código nativo?
El código nativo resulta necesario para acceder a funciones no disponibles en JavaScript o para optimizar rendimientos críticos.
// iOS - Native module in Swift
// ios/MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: NSObject {
@objc
func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let info: [String: Any] = [
"model": UIDevice.current.model,
"systemVersion": UIDevice.current.systemVersion,
"name": UIDevice.current.name
]
resolve(info)
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}
// ios/MyModule.m (Bridge)
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MyModule, NSObject)
RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
// Android - Native module in Kotlin
// android/app/src/main/java/com/myapp/MyModule.kt
package com.myapp
import com.facebook.react.bridge.*
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "MyModule"
@ReactMethod
fun getDeviceInfo(promise: Promise) {
val info = Arguments.createMap().apply {
putString("model", android.os.Build.MODEL)
putString("systemVersion", android.os.Build.VERSION.RELEASE)
putString("manufacturer", android.os.Build.MANUFACTURER)
}
promise.resolve(info)
}
}
// JavaScript - Using native module
import { NativeModules } from 'react-native'
const { MyModule } = NativeModules
async function getDeviceInfo() {
try {
const info = await MyModule.getDeviceInfo()
console.log('Device info:', info)
return info
} catch (error) {
console.error('Error getting device info:', error)
throw error
}
}20. ¿Qué es Expo y cuándo conviene utilizarlo?
Expo es un framework que simplifica el desarrollo en React Native al gestionar la configuración nativa.
// Expo benefits
// - No need for Android Studio or Xcode to start
// - OTA (over-the-air) updates without app stores
// - Rich SDK with preconfigured modules
// - EAS Build for cloud builds
// Creating an Expo project
// npx create-expo-app@latest MyApp
// Using Expo modules
import * as ImagePicker from 'expo-image-picker'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'
async function pickImage() {
// Request permission
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
alert('Permission denied')
return
}
// Open image picker
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8
})
if (!result.canceled) {
return result.assets[0].uri
}
}
// Build configuration
// app.json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.company.myapp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.company.myapp"
},
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Allow camera access"
}
]
]
}
}
// When NOT to use Expo
// - Need for complex custom native modules
// - Integration with specific native SDKs
// - Total control over native configuration
// - Very lightweight application (Expo adds weight)21. ¿Cómo funciona Hot Reloading?
Hot Reloading (Fast Refresh) permite aplicar cambios al instante sin perder el estado de la aplicación.
// Fast Refresh preserves hooks state
function Counter() {
const [count, setCount] = useState(0)
// Modify this text and save
// The count state will be preserved
return (
<View>
<Text>Counter: {count}</Text>
<Button title="+1" onPress={() => setCount(c => c + 1)} />
</View>
)
}
// ⚠️ Cases where Fast Refresh does full reload
// 1. Syntax error
// 2. Modifying a class component
// 3. File with mixed non-component exports
// ❌ This file will do full reload because of mixed exports
export const API_URL = 'https://api.example.com'
export function MyComponent() { /* ... */ }
// ✅ Separate into distinct files
// constants.js
export const API_URL = 'https://api.example.com'
// MyComponent.js
export function MyComponent() { /* ... */ }
// Force remount if needed
// Add this comment at top of file:
// @refresh reset
// Metro configuration for Fast Refresh
// metro.config.js
module.exports = {
transformer: {
experimentalImportSupport: false,
inlineRequires: true
}
}Pruebas y calidad
22. ¿Cómo se prueba una aplicación React Native?
Las pruebas en React Native usan Jest junto con librerías especializadas en renderizado e interacciones.
// Jest configuration
// jest.config.js
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation)/)'
],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js'
}
}
// Component test with React Native Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native'
import { ProductCard } from './ProductCard'
describe('ProductCard', () => {
const mockProduct = {
id: '1',
name: 'iPhone 15',
price: 999,
image: 'https://example.com/iphone.jpg'
}
it('renders product information', () => {
render(<ProductCard product={mockProduct} />)
expect(screen.getByText('iPhone 15')).toBeOnTheScreen()
expect(screen.getByText('$999')).toBeOnTheScreen()
})
it('calls onPress when tapped', () => {
const onPress = jest.fn()
render(<ProductCard product={mockProduct} onPress={onPress} />)
fireEvent.press(screen.getByTestId('product-card'))
expect(onPress).toHaveBeenCalledWith('1')
})
it('shows loading state when adding to cart', async () => {
const onAddToCart = jest.fn(() => new Promise(r => setTimeout(r, 100)))
render(<ProductCard product={mockProduct} onAddToCart={onAddToCart} />)
fireEvent.press(screen.getByText('Add to cart'))
expect(screen.getByTestId('loading-indicator')).toBeOnTheScreen()
await waitFor(() => {
expect(screen.queryByTestId('loading-indicator')).not.toBeOnTheScreen()
})
})
})
// Navigation test
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
function renderWithNavigation(component, { initialRouteName = 'Test' } = {}) {
return render(
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRouteName}>
<Stack.Screen name="Test" component={component} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
// Custom hook test
import { renderHook, act } from '@testing-library/react-native'
import { useCounter } from './useCounter'
test('increments counter', () => {
const { result } = renderHook(() => useCounter(0))
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})23. ¿Cómo se implementan tests E2E con Detox?
Detox permite probar la aplicación sobre simuladores y emuladores reales.
// Installation
// npm install detox --save-dev
// detox init -r jest
// .detoxrc.js
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'e2e/jest.config.js'
},
jest: {
setupTimeout: 120000
}
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/MyApp.app',
build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug'
}
},
devices: {
simulator: {
type: 'ios.simulator',
device: { type: 'iPhone 15' }
},
emulator: {
type: 'android.emulator',
device: { avdName: 'Pixel_5_API_34' }
}
}
}
// e2e/login.test.js
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp()
})
beforeEach(async () => {
await device.reloadReactNative()
})
it('should login successfully with valid credentials', async () => {
// Fill the form
await element(by.id('email-input')).typeText('test@example.com')
await element(by.id('password-input')).typeText('password123')
// Submit
await element(by.id('login-button')).tap()
// Verify navigation to dashboard
await waitFor(element(by.text('Welcome')))
.toBeVisible()
.withTimeout(5000)
})
it('should show error with invalid credentials', async () => {
await element(by.id('email-input')).typeText('wrong@example.com')
await element(by.id('password-input')).typeText('wrongpassword')
await element(by.id('login-button')).tap()
await expect(element(by.text('Invalid credentials'))).toBeVisible()
})
it('should navigate to forgot password', async () => {
await element(by.id('forgot-password-link')).tap()
await expect(element(by.text('Reset Password'))).toBeVisible()
})
})
// Detox commands
// detox build --configuration ios.debug
// detox test --configuration ios.debugDespliegue y producción
24. ¿Cómo se gestionan los entornos (dev, staging, prod)?
La gestión de entornos exige variables de configuración separadas.
// Option 1: react-native-config
// .env.development
API_URL=https://dev-api.myapp.com
ANALYTICS_KEY=dev_key
// .env.staging
API_URL=https://staging-api.myapp.com
ANALYTICS_KEY=staging_key
// .env.production
API_URL=https://api.myapp.com
ANALYTICS_KEY=prod_key
// Usage
import Config from 'react-native-config'
const api = {
baseUrl: Config.API_URL,
analyticsKey: Config.ANALYTICS_KEY
}
// Option 2: JavaScript configuration file
// config/index.js
const ENV = {
development: {
apiUrl: 'https://dev-api.myapp.com',
analyticsEnabled: false,
logLevel: 'debug'
},
staging: {
apiUrl: 'https://staging-api.myapp.com',
analyticsEnabled: true,
logLevel: 'info'
},
production: {
apiUrl: 'https://api.myapp.com',
analyticsEnabled: true,
logLevel: 'error'
}
}
const getEnv = () => {
if (__DEV__) return 'development'
// Logic to determine staging vs prod
return 'production'
}
export const config = ENV[getEnv()]
// Option 3: Expo with app.config.js
// app.config.js
export default ({ config }) => {
const env = process.env.APP_ENV || 'development'
const envConfig = {
development: {
apiUrl: 'https://dev-api.myapp.com',
bundleId: 'com.myapp.dev'
},
production: {
apiUrl: 'https://api.myapp.com',
bundleId: 'com.myapp'
}
}
return {
...config,
extra: {
...envConfig[env],
env
},
ios: {
bundleIdentifier: envConfig[env].bundleId
},
android: {
package: envConfig[env].bundleId
}
}
}25. ¿Cómo se publica en las app stores?
El despliegue implica configurar el build, los metadatos y la propia subida.
# Option 1: EAS Build (Expo)
# Installation
npm install -g eas-cli
# Configuration
eas build:configure
# eas.json
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "your@email.com",
"ascAppId": "1234567890"
},
"android": {
"serviceAccountKeyPath": "./google-services.json",
"track": "production"
}
}
}
}
# Production build
eas build --platform all --profile production
# Store submission
eas submit --platform all --profile production
# Option 2: Fastlane (React Native CLI)
# Gemfile
source "https://rubygems.org"
gem "fastlane"
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Deploy to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(scheme: "MyApp")
upload_to_testflight
end
desc "Deploy to App Store"
lane :release do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(scheme: "MyApp")
upload_to_app_store(
skip_screenshots: true,
skip_metadata: true
)
end
end
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Deploy to Play Store internal"
lane :beta do
gradle(task: "clean bundleRelease")
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end
end26. ¿Cómo se implementan las actualizaciones OTA?
Las actualizaciones Over-The-Air permiten desplegar código JavaScript sin pasar por las stores.
// With Expo Updates
import * as Updates from 'expo-updates'
async function checkForUpdates() {
if (__DEV__) return // Not in development
try {
const update = await Updates.checkForUpdateAsync()
if (update.isAvailable) {
// Download update
await Updates.fetchUpdateAsync()
// Ask user to restart
Alert.alert(
'Update available',
'A new version is available. Restart now?',
[
{ text: 'Later', style: 'cancel' },
{
text: 'Restart',
onPress: () => Updates.reloadAsync()
}
]
)
}
} catch (error) {
console.error('Update check error:', error)
}
}
// Automatic check on startup
function App() {
useEffect(() => {
checkForUpdates()
}, [])
return <AppNavigator />
}
// eas.json configuration for update channels
{
"build": {
"production": {
"channel": "production"
},
"preview": {
"channel": "preview"
}
}
}
// Command to publish an update
// eas update --branch production --message "Bug fix"
// With CodePush (Microsoft)
import codePush from 'react-native-code-push'
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESTART
}
function App() {
return <AppNavigator />
}
export default codePush(codePushOptions)(App)Preguntas avanzadas
27. ¿Cómo se optimiza el tiempo de arranque de la app?
El tiempo de arranque resulta crítico para la experiencia del usuario.
// android/app/build.gradle
project.ext.react = [
enableHermes: true
]
// ios/Podfile
:hermes_enabled => true
// 2. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
// 3. Defer non-critical initializations
import { InteractionManager } from 'react-native'
function App() {
useEffect(() => {
// Execute after first render
InteractionManager.runAfterInteractions(() => {
// Initialize analytics
Analytics.init()
// Prefetch data
prefetchCriticalData()
})
}, [])
return <AppNavigator />
}
// 4. Optimize splash screen
import * as SplashScreen from 'expo-splash-screen'
// Prevent automatic hiding
SplashScreen.preventAutoHideAsync()
function App() {
const [appIsReady, setAppIsReady] = useState(false)
useEffect(() => {
async function prepare() {
try {
// Load critical resources
await Font.loadAsync(customFonts)
await Image.prefetch(criticalImages)
// Restore authentication
await restoreAuth()
} catch (e) {
console.warn(e)
} finally {
setAppIsReady(true)
}
}
prepare()
}, [])
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// Hide splash screen
await SplashScreen.hideAsync()
}
}, [appIsReady])
if (!appIsReady) return null
return (
<View style={{ flex: 1 }} onLayout={onLayoutRootView}>
<AppNavigator />
</View>
)
}
// 5. Inline requires to defer imports
// metro.config.js
module.exports = {
transformer: {
inlineRequires: true
}
}
// Manual usage
function loadHeavyModule() {
const HeavyModule = require('./HeavyModule').default
return HeavyModule
}28. ¿Cómo se aborda la seguridad en una app React Native?
La seguridad móvil requiere varias capas de protección.
import * as SecureStore from 'expo-secure-store'
// or
import * as Keychain from 'react-native-keychain'
async function saveToken(token) {
await SecureStore.setItemAsync('auth_token', token)
}
async function getToken() {
return await SecureStore.getItemAsync('auth_token')
}
// 2. Certificate pinning for network calls
// android/app/src/main/res/xml/network_security_config.xml
/*
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.myapp.com</domain>
<pin-set>
<pin digest="SHA-256">AAAAAA...</pin>
<pin digest="SHA-256">BBBBBB...</pin>
</pin-set>
</domain-config>
</network-security-config>
*/
// 3. Jailbreak/root detection
import JailMonkey from 'jail-monkey'
function SecurityCheck() {
useEffect(() => {
if (JailMonkey.isJailBroken()) {
Alert.alert(
'Insecure device',
'This application cannot run on a rooted/jailbroken device'
)
}
}, [])
}
// 4. Code obfuscation
// metro.config.js (for Hermes)
module.exports = {
transformer: {
minifierConfig: {
mangle: true,
output: {
ascii_only: true
}
}
}
}
// 5. Screenshot/recording protection
import { usePreventScreenCapture } from 'expo-screen-capture'
function SensitiveScreen() {
usePreventScreenCapture() // iOS only
return <View>{/* Sensitive data */}</View>
}
// 6. Session timeout
function useSessionTimeout(timeoutMs = 5 * 60 * 1000) {
const lastActivity = useRef(Date.now())
const { logout } = useAuth()
useEffect(() => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') {
const elapsed = Date.now() - lastActivity.current
if (elapsed > timeoutMs) {
logout()
}
} else {
lastActivity.current = Date.now()
}
})
return () => subscription.remove()
}, [timeoutMs, logout])
}29. ¿Cómo se implementa la accesibilidad?
La accesibilidad resulta esencial para que la app sea utilizable por todo el mundo.
import { AccessibilityInfo } from 'react-native'
// 1. Basic accessibility props
function AccessibleButton({ label, onPress, disabled }) {
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
accessible={true}
accessibilityLabel={label}
accessibilityRole="button"
accessibilityState={{ disabled }}
accessibilityHint={`Tap to ${label.toLowerCase()}`}
>
<Text>{label}</Text>
</TouchableOpacity>
)
}
// 2. Group elements for screen readers
function ProductCard({ product }) {
return (
<View
accessible={true}
accessibilityLabel={`${product.name}, ${product.price} dollars`}
>
<Image
source={{ uri: product.image }}
accessibilityIgnoresInvertColors={true}
/>
<Text>{product.name}</Text>
<Text>${product.price}</Text>
</View>
)
}
// 3. Announce dynamic changes
function NotificationBadge({ count }) {
useEffect(() => {
if (count > 0) {
AccessibilityInfo.announceForAccessibility(
`${count} new notification${count > 1 ? 's' : ''}`
)
}
}, [count])
return (
<View accessibilityLabel={`${count} notifications`}>
<Text>{count}</Text>
</View>
)
}
// 4. Detect accessibility preferences
function useAccessibilityPreferences() {
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false)
const [isReduceMotionEnabled, setIsReduceMotionEnabled] = useState(false)
useEffect(() => {
AccessibilityInfo.isScreenReaderEnabled().then(setIsScreenReaderEnabled)
AccessibilityInfo.isReduceMotionEnabled().then(setIsReduceMotionEnabled)
const screenReaderListener = AccessibilityInfo.addEventListener(
'screenReaderChanged',
setIsScreenReaderEnabled
)
const reduceMotionListener = AccessibilityInfo.addEventListener(
'reduceMotionChanged',
setIsReduceMotionEnabled
)
return () => {
screenReaderListener.remove()
reduceMotionListener.remove()
}
}, [])
return { isScreenReaderEnabled, isReduceMotionEnabled }
}
// 5. Adapt animations based on preferences
function AnimatedComponent() {
const { isReduceMotionEnabled } = useAccessibilityPreferences()
const animation = useSharedValue(0)
useEffect(() => {
animation.value = withTiming(1, {
duration: isReduceMotionEnabled ? 0 : 300
})
}, [isReduceMotionEnabled])
return <Animated.View style={animatedStyle} />
}30. ¿Cómo se estructura un proyecto React Native a gran escala?
Una arquitectura clara facilita el mantenimiento y la escalabilidad.
// Recommended structure
src/
├── app/ # App configuration
│ ├── App.tsx
│ ├── Navigation.tsx
│ └── Providers.tsx
│
├── features/ # Feature modules
│ ├── auth/
│ │ ├── screens/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── components/
│ │ │ └── AuthForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ └── index.ts # Public export
│ │
│ ├── products/
│ │ ├── screens/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ │
│ └── cart/
│ └── ...
│
├── shared/ # Shared code
│ ├── components/
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Card.tsx
│ ├── hooks/
│ │ ├── useDebounce.ts
│ │ └── useNetworkStatus.ts
│ ├── utils/
│ │ ├── format.ts
│ │ └── validation.ts
│ └── types/
│ └── index.ts
│
├── services/ # Global services
│ ├── api/
│ │ ├── client.ts
│ │ └── interceptors.ts
│ ├── storage/
│ │ └── secureStorage.ts
│ └── analytics/
│ └── analytics.ts
│
├── store/ # Global state
│ ├── slices/
│ └── index.ts
│
└── theme/ # Design system
├── colors.ts
├── typography.ts
├── spacing.ts
└── index.ts// Example feature module organization
// features/products/index.ts
export { ProductListScreen } from './screens/ProductListScreen'
export { ProductDetailScreen } from './screens/ProductDetailScreen'
export { useProducts } from './hooks/useProducts'
export { ProductCard } from './components/ProductCard'
// features/products/hooks/useProducts.ts
import { useQuery } from '@tanstack/react-query'
import { productService } from '../services/productService'
export function useProducts(categoryId?: string) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: () => productService.getProducts(categoryId)
})
}
// features/products/services/productService.ts
import { apiClient } from '@/services/api/client'
import { Product } from '../types'
export const productService = {
async getProducts(categoryId?: string): Promise<Product[]> {
const params = categoryId ? { category: categoryId } : {}
const response = await apiClient.get('/products', { params })
return response.data
},
async getProduct(id: string): Promise<Product> {
const response = await apiClient.get(`/products/${id}`)
return response.data
}
}
// Import alias configuration
// babel.config.js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
[
'module-resolver',
{
alias: {
'@': './src',
'@features': './src/features',
'@shared': './src/shared',
'@services': './src/services'
}
}
]
]
}Conclusión
Estas 30 preguntas cubren los conocimientos esenciales de React Native que se evalúan en una entrevista. Puntos clave a dominar:
- Arquitectura: comprender el bridge, JSI y la nueva arquitectura
- Componentes: navegación, listas de alto rendimiento, animaciones
- Estado y datos: Zustand/Redux, React Query, almacenamiento local
- Rendimiento: optimización de FlatList, memoización, profiling
- Particularidades móviles: permisos, notificaciones, deep links
- Pruebas: Jest, Testing Library, Detox
- Despliegue: EAS Build, stores, actualizaciones OTA
- Seguridad y accesibilidad: almacenamiento seguro, conformidad WCAG
Prepararse para una entrevista de React Native exige entender los aspectos específicos del ámbito móvil más allá del conocimiento de React web. Practicar con proyectos reales y probar en dispositivos físicos consolida estos conceptos.
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

React Native: Desarrollo completo de una aplicación móvil en 2026
Guía completa para desarrollar aplicaciones móviles iOS y Android con React Native. Desde la configuración inicial hasta la publicación en tiendas, todos los fundamentos necesarios.

Nueva Arquitectura de React Native en 2026: Hermes V1, Modo Bridgeless y Preguntas de Entrevista
La Nueva Arquitectura de React Native es ahora el estándar en 2026 con Hermes V1, Modo Bridgeless, TurboModules y Fabric. Análisis profundo de las mejoras de rendimiento, patrones de migración y preguntas clave de entrevista.

Expo Router en React Native: Guia Completa de Navegacion Basada en Archivos
Guia completa de Expo Router para React Native en 2026. Navegacion basada en archivos, rutas dinamicas, tabs, modales, typed routes, middleware y proteccion de rutas con ejemplos de codigo.