Top 30 questions d'entretien React Native : guide complet 2026
Les 30 questions d'entretien React Native les plus posées. Réponses détaillées avec exemples de code pour décrocher votre poste de développeur mobile.

Les entretiens techniques React Native évaluent la maîtrise du développement mobile cross-platform, des spécificités iOS/Android, et des patterns de performance. Ce guide présente les 30 questions les plus fréquentes, avec des réponses détaillées et des exemples de code pour une préparation efficace.
Ces questions couvrent les fondamentaux jusqu'aux concepts avancés. Maîtriser l'architecture React Native et comprendre les différences avec React web sont essentiels pour réussir l'entretien.
Fondamentaux React Native
1. Quelle est la différence entre React et React Native ?
React est une bibliothèque pour créer des interfaces web, tandis que React Native permet de développer des applications mobiles natives pour iOS et Android.
La différence fondamentale réside dans le rendu : React utilise le DOM virtuel qui se traduit en éléments HTML, tandis que React Native utilise un bridge qui communique avec les composants natifs de chaque plateforme.
// React (Web) - utilise des éléments HTML
function WebComponent() {
return (
<div className="container">
<span>Texte web</span>
<button onClick={handleClick}>Cliquer</button>
</div>
)
}
// React Native - utilise des composants natifs
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
function NativeComponent() {
return (
<View style={styles.container}>
<Text>Texte natif</Text>
<TouchableOpacity onPress={handlePress}>
<Text>Appuyer</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16
}
})Les composants React Native se traduisent en UIView sur iOS et android.view sur Android, offrant des performances natives.
2. Comment fonctionne l'architecture de React Native ?
React Native utilise une architecture en trois couches : JavaScript, Bridge (ou JSI dans la nouvelle architecture), et Native.
Le code JavaScript s'exécute dans un moteur JS (Hermes ou JavaScriptCore). Les communications avec le code natif passent par un système de sérialisation JSON dans l'ancienne architecture, ou par JSI (JavaScript Interface) dans la nouvelle architecture.
// Ancienne architecture : communication asynchrone via Bridge
// Le Bridge sérialise les messages en JSON entre JS et Native
// Nouvelle architecture (Fabric + TurboModules)
// JSI permet des appels synchrones directs aux modules natifs
// Exemple d'utilisation de TurboModule
import { TurboModuleRegistry } from 'react-native'
// Accès synchrone au module natif
const DeviceInfo = TurboModuleRegistry.get('DeviceInfo')
const deviceName = DeviceInfo.getDeviceName() // Appel synchrone
// Avec Fabric, le rendu est plus fluide
// Les composants peuvent être créés de manière synchrone
// Réduisant les "jank" lors des animationsLa nouvelle architecture améliore significativement les performances en éliminant la sérialisation JSON et en permettant des appels synchrones.
3. Qu'est-ce que le Metro bundler ?
Metro est le bundler JavaScript utilisé par React Native. Il transforme le code source en un bundle optimisé pour l'exécution mobile.
Metro gère la résolution des modules, la transformation du code (via Babel), et le hot reloading pendant le développement.
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
// Configuration personnalisée
config.resolver.assetExts.push('db') // Ajouter des extensions
config.resolver.sourceExts.push('cjs') // Support CommonJS
// Transformer configuration
config.transformer.babelTransformerPath = require.resolve(
'react-native-svg-transformer'
)
// Optimisations pour la production
config.transformer.minifierConfig = {
keep_classnames: true,
keep_fnames: true,
mangle: {
keep_classnames: true,
keep_fnames: true
}
}
module.exports = configMetro supporte le Fast Refresh, permettant de voir les changements instantanément sans perdre l'état de l'application.
4. Expliquez StyleSheet.create et ses avantages
StyleSheet.create optimise les styles en les validant et en les convertissant en références numériques, réduisant la charge sur le bridge.
// ❌ Styles inline - recréés à chaque rendu
function BadExample() {
return (
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Titre</Text>
</View>
)
}
// ✅ StyleSheet.create - optimisé et validé
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff'
},
title: {
fontSize: 18,
fontWeight: 'bold'
},
// Composition de styles
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 8
}
})
function GoodExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>Titre</Text>
{/* Combinaison de styles */}
<View style={[styles.row, { marginTop: 10 }]}>
<Text>Contenu</Text>
</View>
</View>
)
}
// StyleSheet.absoluteFillObject pour le positionnement absolu
const overlayStyles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)'
}
})5. Quelle est la différence entre Flexbox web et React Native ?
React Native utilise Flexbox mais avec des valeurs par défaut différentes du web, adaptées aux interfaces mobiles verticales.
// Différences clés avec le web
const styles = StyleSheet.create({
container: {
// flexDirection: 'column' par défaut (vs 'row' sur le web)
// alignItems: 'stretch' par défaut
flex: 1
},
// Flexbox React Native
row: {
flexDirection: 'row', // Horizontal
justifyContent: 'space-between', // Axe principal
alignItems: 'center', // Axe secondaire
flexWrap: 'wrap', // Retour à la ligne
gap: 8 // Supporté depuis RN 0.71
},
// Flex grow/shrink
flexItem: {
flex: 1, // Équivalent à flex: 1 1 0
flexGrow: 1, // Grandir pour remplir
flexShrink: 0, // Ne pas rétrécir
flexBasis: 100 // Taille de base
},
// Positionnement absolu
absolute: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
})
// Exemple pratique : carte avec image et contenu
function Card() {
return (
<View style={cardStyles.container}>
<Image source={{ uri: imageUrl }} style={cardStyles.image} />
<View style={cardStyles.content}>
<Text style={cardStyles.title}>Titre</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, // Prend l'espace restant
padding: 12,
justifyContent: 'center'
},
title: {
fontSize: 16,
fontWeight: '600'
},
description: {
fontSize: 14,
color: '#666'
}
})Navigation et composants
6. Comment implémenter la navigation avec React Navigation ?
React Navigation est la solution standard pour la navigation dans React Native. Elle offre plusieurs types de navigateurs adaptés aux patterns mobiles.
// Installation des dépendances
// 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'
// Typage TypeScript pour les paramètres de navigation
type RootStackParamList = {
Home: undefined
Profile: { userId: string }
Settings: { section?: string }
}
const Stack = createNativeStackNavigator<RootStackParamList>()
const Tab = createBottomTabNavigator()
// Navigation par onglets
function TabNavigator() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
// Icône dynamique selon l'onglet
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>
)
}
// Navigation en pile
function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
animation: 'slide_from_right' // Animation native
}}
>
<Stack.Screen
name="Home"
component={TabNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({
title: `Profil ${route.params.userId}`
})}
/>
</Stack.Navigator>
</NavigationContainer>
)
}7. Comment gérer les listes performantes avec FlatList ?
FlatList est optimisé pour les longues listes avec virtualisation automatique, ne rendant que les éléments visibles.
import { FlatList, RefreshControl } from 'react-native'
function ProductList() {
const [products, setProducts] = useState([])
const [refreshing, setRefreshing] = useState(false)
const [loading, setLoading] = useState(false)
// Chargement initial
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)
}, [])
// Pagination infinie
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])
// Rendu d'un élément
const renderItem = useCallback(({ item }) => (
<ProductCard product={item} />
), [])
// Extraction de la clé
const keyExtractor = useCallback((item) => item.id.toString(), [])
// Séparateur entre éléments
const ItemSeparator = useCallback(() => (
<View style={{ height: 12 }} />
), [])
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
ItemSeparatorComponent={ItemSeparator}
// Optimisations de performance
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
windowSize={5}
// Pull-to-refresh
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#007AFF"
/>
}
// Pagination infinie
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <ActivityIndicator /> : null}
// Liste vide
ListEmptyComponent={<EmptyState message="Aucun produit" />}
/>
)
}Toujours mémoriser renderItem avec useCallback et extraire les composants lourds. Éviter les fonctions inline dans renderItem qui causent des re-renders inutiles.
8. Quelle est la différence entre TouchableOpacity, Pressable et TouchableHighlight ?
Ces composants gèrent les interactions tactiles avec des feedbacks visuels différents.
import {
TouchableOpacity,
TouchableHighlight,
Pressable,
StyleSheet
} from 'react-native'
function InteractionExamples() {
return (
<View style={styles.container}>
{/* TouchableOpacity : réduit l'opacité au toucher */}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => console.log('Pressed')}
style={styles.button}
>
<Text>TouchableOpacity</Text>
</TouchableOpacity>
{/* TouchableHighlight : ajoute une couleur de fond */}
<TouchableHighlight
underlayColor="#ddd"
onPress={() => console.log('Pressed')}
style={styles.button}
>
<Text>TouchableHighlight</Text>
</TouchableHighlight>
{/* Pressable : API moderne avec plus de contrôle */}
<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 ? 'Pressé !' : 'Pressable'}
</Text>
)}
</Pressable>
{/* Pressable avec hitSlop pour agrandir la zone tactile */}
<Pressable
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
onPress={() => console.log('Pressed')}
style={styles.smallButton}
>
<Text>Petit bouton</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 est recommandé pour les nouveaux projets car il offre plus de contrôle et une API plus cohérente.
9. Comment créer des animations fluides ?
React Native propose plusieurs API d'animation : Animated (intégré) et Reanimated (plus performant).
import { Animated, Easing } from 'react-native'
import Reanimated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming
} from 'react-native-reanimated'
// Animation avec Animated (API native)
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 sur le thread UI
}).start()
}, [])
return (
<Animated.View style={{ opacity: fadeAnim }}>
{children}
</Animated.View>
)
}
// Animation avec Reanimated (recommandé pour animations complexes)
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}>Appuyer</Text>
</Reanimated.View>
</Pressable>
)
}
// Animation de liste avec LayoutAnimation
import { LayoutAnimation, UIManager, Platform } from 'react-native'
// Activer sur Android
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental?.(true)
}
function AnimatedList() {
const [items, setItems] = useState([])
const addItem = () => {
// Configurer l'animation avant le changement d'état
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="Ajouter" 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>
)
}Prêt à réussir tes entretiens React Native ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Gestion de l'état et données
10. Comment gérer l'état global dans React Native ?
Les mêmes solutions que React web s'appliquent : Context API, Redux, Zustand, ou MobX.
// Solution légère avec Zustand
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'
// Store avec persistance
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)
}
)
)
// Utilisation dans un composant
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>Déconnexion</Text>
</TouchableOpacity>
</View>
)
}
// Store pour le panier
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. Comment effectuer des appels API avec gestion du cache ?
React Query (TanStack Query) est la solution recommandée pour la gestion des données serveur.
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'
// Configuration du client
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
refetchOnWindowFocus: false // Mobile n'a pas de "window focus"
}
}
})
// Provider dans App.tsx
function App() {
return (
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</QueryClientProvider>
)
}
// Hook personnalisé pour les produits
function useProducts(categoryId) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: async () => {
const response = await fetch(`/api/products?category=${categoryId}`)
if (!response.ok) throw new Error('Erreur réseau')
return response.json()
},
enabled: !!categoryId // Ne pas exécuter si pas de categoryId
})
}
// Mutation avec invalidation de cache
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: () => {
// Invalider le cache du panier pour forcer un refetch
queryClient.invalidateQueries({ queryKey: ['cart'] })
},
onError: (error) => {
Alert.alert('Erreur', error.message)
}
})
}
// Utilisation dans un composant
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. Comment stocker des données localement ?
Plusieurs options existent selon le type de données : AsyncStorage pour les données simples, MMKV pour les performances, et SQLite pour les données structurées.
// AsyncStorage - simple mais lent pour gros 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 - très performant (recommandé)
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)
},
// Support des types primitifs
getNumber(key) {
return mmkv.getNumber(key)
},
setNumber(key, value) {
mmkv.set(key, value)
},
getBoolean(key) {
return mmkv.getBoolean(key)
}
}
// Hook pour utiliser MMKV avec 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]
}
// Utilisation
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>
)
}Pour les tokens et données sensibles, utiliser expo-secure-store ou react-native-keychain qui chiffrent les données via Keychain (iOS) et Keystore (Android).
Performance et optimisation
13. Comment optimiser les performances d'une app React Native ?
L'optimisation passe par plusieurs aspects : rendu, mémoire, et interactions.
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) => {
// Comparaison personnalisée
return prevProps.product.id === nextProps.product.id
})
// 2. Mémoriser les callbacks
function ProductList({ products }) {
// ❌ Nouvelle fonction à chaque rendu
// onPress={(id) => handlePress(id)}
// ✅ Fonction stable
const handlePress = useCallback((id) => {
navigation.navigate('Product', { id })
}, [navigation])
return (
<FlatList
data={products}
renderItem={({ item }) => (
<ProductCard product={item} onPress={handlePress} />
)}
/>
)
}
// 3. Optimiser les 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. Utiliser InteractionManager pour les tâches lourdes
import { InteractionManager } from 'react-native'
function HeavyScreen() {
const [data, setData] = useState(null)
useEffect(() => {
// Attendre que les animations soient terminées
const task = InteractionManager.runAfterInteractions(() => {
const result = performHeavyComputation()
setData(result)
})
return () => task.cancel()
}, [])
return data ? <DataView data={data} /> : <LoadingView />
}
// 5. Lazy loading des écrans
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
function Navigator() {
return (
<Stack.Navigator>
<Stack.Screen
name="Heavy"
component={HeavyScreen}
options={{ lazy: true }}
/>
</Stack.Navigator>
)
}14. Comment débugger les problèmes de performance ?
React Native propose plusieurs outils pour identifier les goulots d'étranglement.
// Configuration dans android/app/build.gradle et ios/Podfile
// Voir : https://fbflipper.com/
// 2. Console.time pour mesurer les opérations
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)
// Affiche FPS JS et UI
// 4. Hermes profiler pour le CPU
// Activer dans metro.config.js
module.exports = {
transformer: {
hermesParser: true
}
}
// 5. Identifier les re-renders avec 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
})
}
// Marquer un composant à surveiller
ProductCard.whyDidYouRender = true
// 6. Mesurer le temps de montage
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])
}
// Utilisation
function MyComponent() {
useComponentTiming('MyComponent')
// ...
}15. Comment gérer le mode hors ligne ?
La gestion offline nécessite une stratégie de cache et de synchronisation.
import NetInfo from '@react-native-community/netinfo'
// Hook pour surveiller la connectivité
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 avec queue offline
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()
// Composant indicateur de statut réseau
function NetworkBanner() {
const { isConnected } = useNetworkStatus()
if (isConnected) return null
return (
<View style={styles.banner}>
<Text style={styles.bannerText}>
Mode hors ligne - Les modifications seront synchronisées
</Text>
</View>
)
}Spécificités mobiles
16. Comment gérer les permissions sur iOS et Android ?
Les permissions sont gérées différemment sur chaque plateforme. Des bibliothèques comme react-native-permissions unifient l'API.
import { Platform, Alert, Linking } from 'react-native'
import {
check,
request,
PERMISSIONS,
RESULTS,
openSettings
} from 'react-native-permissions'
// Configuration des permissions par plateforme
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 pour gérer les 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 requise',
`Cette fonctionnalité nécessite l'accès à ${type}. Voulez-vous ouvrir les paramètres ?`,
[
{ text: 'Non', style: 'cancel' },
{ text: 'Ouvrir', onPress: openSettings }
]
)
}
return result
}, [permission, type])
return {
status,
isGranted: status === RESULTS.GRANTED,
isDenied: status === RESULTS.DENIED,
isBlocked: status === RESULTS.BLOCKED,
requestPermission
}
}
// Utilisation
function CameraButton() {
const { isGranted, requestPermission } = usePermission('camera')
const handlePress = async () => {
if (!isGranted) {
const result = await requestPermission()
if (result !== RESULTS.GRANTED) return
}
// Ouvrir la caméra
navigation.navigate('Camera')
}
return (
<TouchableOpacity onPress={handlePress}>
<Text>Prendre une photo</Text>
</TouchableOpacity>
)
}17. Comment implémenter les notifications push ?
Les notifications push nécessitent une configuration native et un backend pour l'envoi.
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance } from '@notifee/react-native'
// Service de notifications
class NotificationService {
async initialize() {
// Demander la permission (iOS)
const authStatus = await messaging().requestPermission()
const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED
if (enabled) {
// Récupérer le token FCM
const token = await messaging().getToken()
await this.registerToken(token)
// Écouter les changements de token
messaging().onTokenRefresh(this.registerToken)
// Créer un canal Android
await notifee.createChannel({
id: 'default',
name: 'Notifications',
importance: AndroidImportance.HIGH
})
}
return enabled
}
async registerToken(token) {
// Envoyer le token au backend
await api.registerPushToken(token)
}
// Configurer les handlers
setupHandlers() {
// Notification reçue en foreground
messaging().onMessage(async (remoteMessage) => {
await this.displayNotification(remoteMessage)
})
// Notification tapée (app en background)
messaging().onNotificationOpenedApp((remoteMessage) => {
this.handleNotificationPress(remoteMessage)
})
// App ouverte depuis notification (app fermée)
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
}
}
}
// Utilisation dans App.tsx
function App() {
useEffect(() => {
const notificationService = new NotificationService()
notificationService.initialize()
notificationService.setupHandlers()
}, [])
return <AppNavigator />
}18. Comment gérer les deep links ?
Les deep links permettent d'ouvrir l'app à un écran spécifique depuis une URL externe.
import { Linking } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
// Configuration des deep links
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'
}
}
}
},
// Fonction personnalisée pour récupérer l'URL initiale
async getInitialURL() {
// Vérifier si l'app a été ouverte via un deep link
const url = await Linking.getInitialURL()
if (url) return url
// Vérifier les notifications
const message = await messaging().getInitialNotification()
if (message?.data?.link) return message.data.link
return null
},
// Souscrire aux liens entrants
subscribe(listener) {
// Deep links standard
const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
listener(url)
})
// Liens depuis notifications
const unsubscribeNotification = messaging().onNotificationOpenedApp(
(message) => {
const link = message.data?.link
if (link) listener(link)
}
)
return () => {
linkingSubscription.remove()
unsubscribeNotification()
}
}
}
// Utilisation
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>
)
}
// Test des deep links
// myapp://product/123
// https://myapp.com/profile/user456Prêt à réussir tes entretiens React Native ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Code natif et modules
19. Quand et comment écrire du code natif ?
Le code natif est nécessaire pour accéder à des fonctionnalités non disponibles en JavaScript ou pour optimiser les performances critiques.
// iOS - Module natif en 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 - Module natif en 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 - Utilisation du module natif
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'est-ce qu'Expo et quand l'utiliser ?
Expo est un framework qui simplifie le développement React Native en gérant la configuration native.
// Avantages d'Expo
// - Pas besoin d'Android Studio ou Xcode pour démarrer
// - Updates OTA (over-the-air) sans app stores
// - SDK riche avec modules préconfigurés
// - EAS Build pour les builds cloud
// Installation d'un projet Expo
// npx create-expo-app@latest MyApp
// Utilisation des modules Expo
import * as ImagePicker from 'expo-image-picker'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'
async function pickImage() {
// Demander la permission
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
alert('Permission refusée')
return
}
// Ouvrir le sélecteur d'images
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
}
}
// Configuration pour les builds
// 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": "Autoriser l'accès à la caméra"
}
]
]
}
}
// Quand NE PAS utiliser Expo
// - Besoin de modules natifs personnalisés complexes
// - Intégration avec SDK natifs spécifiques
// - Contrôle total sur la configuration native
// - Application très légère (Expo ajoute du poids)21. Comment fonctionne le Hot Reloading ?
Le Hot Reloading (Fast Refresh) permet de voir les changements instantanément sans perdre l'état de l'application.
// Fast Refresh préserve l'état des hooks
function Counter() {
const [count, setCount] = useState(0)
// Modifier ce texte et sauvegarder
// L'état count sera préservé
return (
<View>
<Text>Compteur: {count}</Text>
<Button title="+1" onPress={() => setCount(c => c + 1)} />
</View>
)
}
// ⚠️ Cas où Fast Refresh fait un full reload
// 1. Erreur de syntaxe
// 2. Modification d'un composant classe
// 3. Fichier avec exports non-composants mélangés
// ❌ Ce fichier fera un full reload car mélange export
export const API_URL = 'https://api.example.com'
export function MyComponent() { /* ... */ }
// ✅ Séparer en fichiers distincts
// constants.js
export const API_URL = 'https://api.example.com'
// MyComponent.js
export function MyComponent() { /* ... */ }
// Forcer un remount si nécessaire
// Ajouter ce commentaire en haut du fichier :
// @refresh reset
// Configuration Metro pour Fast Refresh
// metro.config.js
module.exports = {
transformer: {
experimentalImportSupport: false,
inlineRequires: true
}
}Tests et qualité
22. Comment tester une application React Native ?
Les tests React Native utilisent Jest avec des librairies spécialisées pour le rendu et les interactions.
// Configuration Jest
// 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'
}
}
// Test de composant avec 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('Ajouter au panier'))
expect(screen.getByTestId('loading-indicator')).toBeOnTheScreen()
await waitFor(() => {
expect(screen.queryByTestId('loading-indicator')).not.toBeOnTheScreen()
})
})
})
// Test de navigation
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>
)
}
// Test de hook personnalisé
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. Comment implémenter les tests E2E avec Detox ?
Detox permet de tester l'application sur de vrais simulateurs/émulateurs.
// 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 () => {
// Remplir le formulaire
await element(by.id('email-input')).typeText('test@example.com')
await element(by.id('password-input')).typeText('password123')
// Soumettre
await element(by.id('login-button')).tap()
// Vérifier la navigation vers le dashboard
await waitFor(element(by.text('Bienvenue')))
.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('Identifiants incorrects'))).toBeVisible()
})
it('should navigate to forgot password', async () => {
await element(by.id('forgot-password-link')).tap()
await expect(element(by.text('Réinitialiser le mot de passe'))).toBeVisible()
})
})
// Commandes Detox
// detox build --configuration ios.debug
// detox test --configuration ios.debugDéploiement et production
24. Comment gérer les environnements (dev, staging, prod) ?
La gestion des environnements nécessite des variables de configuration distinctes.
// 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
// Utilisation
import Config from 'react-native-config'
const api = {
baseUrl: Config.API_URL,
analyticsKey: Config.ANALYTICS_KEY
}
// Option 2 : Fichier de configuration JavaScript
// 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'
// Logique pour déterminer staging vs prod
return 'production'
}
export const config = ENV[getEnv()]
// Option 3 : Expo avec 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. Comment déployer sur les app stores ?
Le déploiement implique la configuration des builds, les métadonnées, et la soumission.
# 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"
}
}
}
}
# Build de production
eas build --platform all --profile production
# Soumission aux stores
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. Comment implémenter les mises à jour OTA ?
Les mises à jour Over-The-Air permettent de déployer du code JavaScript sans passer par les stores.
// Avec Expo Updates
import * as Updates from 'expo-updates'
async function checkForUpdates() {
if (__DEV__) return // Pas en développement
try {
const update = await Updates.checkForUpdateAsync()
if (update.isAvailable) {
// Télécharger la mise à jour
await Updates.fetchUpdateAsync()
// Demander à l'utilisateur de redémarrer
Alert.alert(
'Mise à jour disponible',
'Une nouvelle version est disponible. Redémarrer maintenant ?',
[
{ text: 'Plus tard', style: 'cancel' },
{
text: 'Redémarrer',
onPress: () => Updates.reloadAsync()
}
]
)
}
} catch (error) {
console.error('Erreur de vérification des mises à jour:', error)
}
}
// Vérification automatique au démarrage
function App() {
useEffect(() => {
checkForUpdates()
}, [])
return <AppNavigator />
}
// Configuration eas.json pour les canaux de mise à jour
{
"build": {
"production": {
"channel": "production"
},
"preview": {
"channel": "preview"
}
}
}
// Commande pour publier une mise à jour
// eas update --branch production --message "Bug fix"
// Avec 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)Questions avancées
27. Comment optimiser le temps de démarrage de l'app ?
Le temps de démarrage est critique pour l'expérience utilisateur.
// android/app/build.gradle
project.ext.react = [
enableHermes: true
]
// ios/Podfile
:hermes_enabled => true
// 2. Lazy loading des écrans
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
// 3. Différer les initialisations non-critiques
import { InteractionManager } from 'react-native'
function App() {
useEffect(() => {
// Exécuter après le premier rendu
InteractionManager.runAfterInteractions(() => {
// Initialiser analytics
Analytics.init()
// Précharger les données
prefetchCriticalData()
})
}, [])
return <AppNavigator />
}
// 4. Optimiser le splash screen
import * as SplashScreen from 'expo-splash-screen'
// Empêcher le masquage automatique
SplashScreen.preventAutoHideAsync()
function App() {
const [appIsReady, setAppIsReady] = useState(false)
useEffect(() => {
async function prepare() {
try {
// Charger les ressources critiques
await Font.loadAsync(customFonts)
await Image.prefetch(criticalImages)
// Restaurer l'authentification
await restoreAuth()
} catch (e) {
console.warn(e)
} finally {
setAppIsReady(true)
}
}
prepare()
}, [])
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// Masquer le splash screen
await SplashScreen.hideAsync()
}
}, [appIsReady])
if (!appIsReady) return null
return (
<View style={{ flex: 1 }} onLayout={onLayoutRootView}>
<AppNavigator />
</View>
)
}
// 5. Inline requires pour différer les imports
// metro.config.js
module.exports = {
transformer: {
inlineRequires: true
}
}
// Utilisation manuelle
function loadHeavyModule() {
const HeavyModule = require('./HeavyModule').default
return HeavyModule
}28. Comment gérer la sécurité dans une app React Native ?
La sécurité mobile nécessite plusieurs couches de protection.
import * as SecureStore from 'expo-secure-store'
// ou
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 pour les appels réseau
// 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. Détection de jailbreak/root
import JailMonkey from 'jail-monkey'
function SecurityCheck() {
useEffect(() => {
if (JailMonkey.isJailBroken()) {
Alert.alert(
'Appareil non sécurisé',
'Cette application ne peut pas fonctionner sur un appareil rooté/jailbreaké'
)
}
}, [])
}
// 4. Obfuscation du code
// metro.config.js (pour Hermes)
module.exports = {
transformer: {
minifierConfig: {
mangle: true,
output: {
ascii_only: true
}
}
}
}
// 5. Protection contre le screenshot/recording
import { usePreventScreenCapture } from 'expo-screen-capture'
function SensitiveScreen() {
usePreventScreenCapture() // iOS uniquement
return <View>{/* Données sensibles */}</View>
}
// 6. Timeout de session
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. Comment implémenter l'accessibilité ?
L'accessibilité est essentielle pour rendre l'app utilisable par tous.
import { AccessibilityInfo } from 'react-native'
// 1. Props d'accessibilité de base
function AccessibleButton({ label, onPress, disabled }) {
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
accessible={true}
accessibilityLabel={label}
accessibilityRole="button"
accessibilityState={{ disabled }}
accessibilityHint={`Appuyer pour ${label.toLowerCase()}`}
>
<Text>{label}</Text>
</TouchableOpacity>
)
}
// 2. Grouper les éléments pour les lecteurs d'écran
function ProductCard({ product }) {
return (
<View
accessible={true}
accessibilityLabel={`${product.name}, ${product.price} euros`}
>
<Image
source={{ uri: product.image }}
accessibilityIgnoresInvertColors={true}
/>
<Text>{product.name}</Text>
<Text>{product.price}€</Text>
</View>
)
}
// 3. Annoncer les changements dynamiques
function NotificationBadge({ count }) {
useEffect(() => {
if (count > 0) {
AccessibilityInfo.announceForAccessibility(
`${count} nouvelle${count > 1 ? 's' : ''} notification${count > 1 ? 's' : ''}`
)
}
}, [count])
return (
<View accessibilityLabel={`${count} notifications`}>
<Text>{count}</Text>
</View>
)
}
// 4. Détecter les préférences d'accessibilité
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. Adapter les animations selon les préférences
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. Comment structurer un projet React Native à grande échelle ?
Une architecture claire facilite la maintenance et la scalabilité.
// Structure recommandée
src/
├── app/ # Configuration de l'app
│ ├── App.tsx
│ ├── Navigation.tsx
│ └── Providers.tsx
│
├── features/ # Modules fonctionnels
│ ├── auth/
│ │ ├── screens/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── components/
│ │ │ └── AuthForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ └── index.ts # Export public
│ │
│ ├── products/
│ │ ├── screens/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ │
│ └── cart/
│ └── ...
│
├── shared/ # Code partagé
│ ├── components/
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Card.tsx
│ ├── hooks/
│ │ ├── useDebounce.ts
│ │ └── useNetworkStatus.ts
│ ├── utils/
│ │ ├── format.ts
│ │ └── validation.ts
│ └── types/
│ └── index.ts
│
├── services/ # Services globaux
│ ├── api/
│ │ ├── client.ts
│ │ └── interceptors.ts
│ ├── storage/
│ │ └── secureStorage.ts
│ └── analytics/
│ └── analytics.ts
│
├── store/ # État global
│ ├── slices/
│ └── index.ts
│
└── theme/ # Design system
├── colors.ts
├── typography.ts
├── spacing.ts
└── index.ts// Exemple d'organisation d'un feature module
// 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
}
}
// Configuration des alias d'import
// 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'
}
}
]
]
}Conclusion
Ces 30 questions couvrent l'essentiel des connaissances React Native attendues en entretien. Les points clés à maîtriser :
- ✅ Architecture : Comprendre le bridge, JSI, et la nouvelle architecture
- ✅ Composants : Navigation, listes performantes, animations
- ✅ État et données : Zustand/Redux, React Query, stockage local
- ✅ Performance : Optimisation FlatList, mémorisation, profilage
- ✅ Spécificités mobiles : Permissions, notifications, deep links
- ✅ Tests : Jest, Testing Library, Detox
- ✅ Déploiement : EAS Build, stores, mises à jour OTA
- ✅ Sécurité et accessibilité : Stockage sécurisé, WCAG compliance
La préparation aux entretiens React Native nécessite une compréhension des spécificités mobiles au-delà des connaissances React web. Pratiquer avec des projets réels et tester sur de vrais appareils consolide ces concepts.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

React Native : Créer une app mobile complète en 2026
Guide complet pour développer une application mobile iOS et Android avec React Native. De la configuration à la publication, tous les fondamentaux pour démarrer.

Nouvelle Architecture React Native en 2026 : Hermes V1, Mode Bridgeless et Questions d'Entretien
La Nouvelle Architecture React Native est désormais le standard en 2026 avec Hermes V1, le mode Bridgeless, les TurboModules et Fabric. Analyse approfondie des gains de performance, des patterns de migration et des questions d'entretien incontournables.

Expo Router pour React Native : Guide Complet de la Navigation par Fichiers
Expo Router apporte le routage basé sur les fichiers à React Native, inspiré de Next.js. Guide complet avec configuration, navigation par onglets, routes dynamiques, modales, middleware et protection des routes.