# 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. - Published: 2026-01-10 - Updated: 2026-03-28 - Author: SharpSkill - Tags: react native, mobile development, javascript, ios, android - Reading time: 15 min --- React Native permet de créer des applications mobiles natives pour iOS et Android avec une seule base de code JavaScript. Maintenu par Meta et utilisé par des applications comme Instagram, Facebook et Discord, ce framework offre une expérience de développement rapide tout en produisant des apps performantes. > **Pourquoi React Native en 2026 ?** > > Avec la nouvelle architecture (Fabric et TurboModules) maintenant stable, React Native offre des performances proches du natif tout en gardant la productivité du développement web. Plus de 40% des applications du top 500 utilisent React Native. ## Configuration de l'environnement de développement Avant de commencer, l'installation des outils nécessaires est indispensable. React Native propose deux approches : Expo (recommandé pour débuter) et React Native CLI (pour plus de contrôle). ```bash # setup.sh # Installation de Node.js (version LTS recommandée) # Vérifier la version installée node --version # >= 18.x requis npm --version # >= 9.x requis # Installation de l'outil Expo CLI globalement npm install -g expo-cli # Création d'un nouveau projet avec Expo npx create-expo-app@latest MonApp --template blank-typescript # Navigation vers le projet cd MonApp # Lancement du serveur de développement npx expo start ``` Expo simplifie considérablement le développement en gérant automatiquement la configuration native. Pour tester l'application, l'app Expo Go sur smartphone permet de scanner le QR code affiché. ```bash # structure.sh # Structure du projet généré MonApp/ ├── App.tsx # Point d'entrée de l'application ├── app.json # Configuration Expo ├── package.json # Dépendances ├── tsconfig.json # Configuration TypeScript ├── babel.config.js # Configuration Babel └── assets/ # Images et ressources ├── icon.png └── splash.png ``` ## Composants fondamentaux de React Native React Native fournit des composants natifs qui remplacent les éléments HTML. Chaque composant se traduit directement en composant natif iOS ou Android. ```tsx // App.tsx import React from 'react'; import { View, Text, StyleSheet, SafeAreaView, StatusBar } from 'react-native'; // Composant principal de l'application export default function App() { return ( // SafeAreaView évite le chevauchement avec la barre de statut {/* StatusBar configure l'apparence de la barre système */} {/* View est l'équivalent de div */} {/* Text est obligatoire pour afficher du texte */} Bienvenue sur MonApp Une application React Native ); } // StyleSheet.create optimise les styles pour le natif const styles = StyleSheet.create({ container: { flex: 1, // Prend tout l'espace disponible backgroundColor: '#ffffff', }, header: { padding: 20, alignItems: 'center', // Centre horizontalement justifyContent: 'center', // Centre verticalement }, title: { fontSize: 24, fontWeight: 'bold', color: '#1a1a1a', marginBottom: 8, }, subtitle: { fontSize: 16, color: '#666666', }, }); ``` > **Flexbox par défaut** > > React Native utilise Flexbox avec `flexDirection: 'column'` par défaut (contrairement au web qui utilise `row`). Cette différence est importante pour la mise en page. ## Gestion des interactions utilisateur Les interactions tactiles sont gérées via des composants spécialisés. Chaque type d'interaction a son composant dédié pour optimiser les performances. ```tsx // components/InteractiveButton.tsx import React, { useState } from 'react'; import { TouchableOpacity, TouchableHighlight, Pressable, Text, StyleSheet, View, } from 'react-native'; // Composant bouton avec différents styles d'interaction export function InteractiveButton() { const [count, setCount] = useState(0); return ( {/* TouchableOpacity réduit l'opacité au toucher */} setCount(c => c + 1)} > Compteur: {count} {/* Pressable offre plus de contrôle sur les états */} [ styles.button, styles.pressableButton, pressed && styles.buttonPressed, ]} onPress={() => console.log('Pressed!')} onLongPress={() => console.log('Long press!')} > {({ pressed }) => ( {pressed ? 'Pressé!' : 'Appuyer ici'} )} ); } const styles = StyleSheet.create({ container: { gap: 16, padding: 20, }, button: { backgroundColor: '#007AFF', paddingVertical: 14, paddingHorizontal: 28, borderRadius: 10, alignItems: 'center', }, pressableButton: { backgroundColor: '#34C759', }, buttonPressed: { backgroundColor: '#2DA44E', transform: [{ scale: 0.98 }], }, buttonText: { color: '#ffffff', fontSize: 16, fontWeight: '600', }, }); ``` ## Navigation entre écrans avec React Navigation La navigation est essentielle pour toute application mobile. React Navigation est la solution standard, offrant différents types de navigation adaptés aux patterns mobiles. ```bash # install-navigation.sh # Installation des dépendances de navigation npx expo install @react-navigation/native npx expo install @react-navigation/native-stack npx expo install react-native-screens react-native-safe-area-context ``` ```tsx // App.tsx import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; // Import des écrans import { HomeScreen } from './screens/HomeScreen'; import { DetailScreen } from './screens/DetailScreen'; import { ProfileScreen } from './screens/ProfileScreen'; // Définition des types pour la navigation TypeScript export type RootStackParamList = { Home: undefined; // Pas de paramètres Detail: { itemId: number; title: string }; // Paramètres requis Profile: { userId?: string }; // Paramètre optionnel }; // Création du navigator avec typage const Stack = createNativeStackNavigator(); export default function App() { return ( // NavigationContainer gère l'état de navigation ({ title: route.params.title })} /> ); } ``` ```tsx // screens/HomeScreen.tsx import React from 'react'; import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { RootStackParamList } from '../App'; // Typage des props de navigation type Props = NativeStackScreenProps; // Données d'exemple const ITEMS = [ { id: 1, title: 'Premier élément' }, { id: 2, title: 'Deuxième élément' }, { id: 3, title: 'Troisième élément' }, ]; export function HomeScreen({ navigation }: Props) { return ( {/* FlatList pour les listes performantes */} item.id.toString()} renderItem={({ item }) => ( { // Navigation avec paramètres typés navigation.navigate('Detail', { itemId: item.id, title: item.title, }); }} > {item.title} )} ItemSeparatorComponent={() => } /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, item: { backgroundColor: '#ffffff', padding: 20, }, itemText: { fontSize: 16, color: '#1a1a1a', }, separator: { height: 1, backgroundColor: '#e0e0e0', }, }); ``` ## Gestion de l'état avec Context et hooks Pour une gestion d'état simple à moyenne, React Context combiné aux hooks offre une solution efficace sans dépendance externe. ```tsx // context/AuthContext.tsx import React, { createContext, useContext, useState, useCallback } from 'react'; // Types pour l'authentification interface User { id: string; email: string; name: string; } interface AuthContextType { user: User | null; isLoading: boolean; signIn: (email: string, password: string) => Promise; signOut: () => void; } // Création du contexte avec valeur par défaut const AuthContext = createContext(undefined); // Provider qui encapsule l'application export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(false); // Fonction de connexion const signIn = useCallback(async (email: string, password: string) => { setIsLoading(true); try { // Simulation d'appel API const response = await fetch('https://api.example.com/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); const data = await response.json(); setUser(data.user); } catch (error) { console.error('Erreur de connexion:', error); throw error; } finally { setIsLoading(false); } }, []); // Fonction de déconnexion const signOut = useCallback(() => { setUser(null); }, []); return ( {children} ); } // Hook personnalisé pour utiliser le contexte export function useAuth() { const context = useContext(AuthContext); if (context === undefined) { throw new Error('useAuth doit être utilisé dans un AuthProvider'); } return context; } ``` ```tsx // screens/LoginScreen.tsx import React, { useState } from 'react'; import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, Alert, } from 'react-native'; import { useAuth } from '../context/AuthContext'; export function LoginScreen() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const { signIn, isLoading } = useAuth(); // Gestion de la soumission du formulaire const handleSubmit = async () => { if (!email || !password) { Alert.alert('Erreur', 'Veuillez remplir tous les champs'); return; } try { await signIn(email, password); } catch (error) { Alert.alert('Erreur', 'Identifiants incorrects'); } }; return ( Connexion {isLoading ? ( ) : ( Se connecter )} ); } const styles = StyleSheet.create({ container: { flex: 1, padding: 20, justifyContent: 'center', backgroundColor: '#ffffff', }, title: { fontSize: 28, fontWeight: 'bold', marginBottom: 32, textAlign: 'center', }, input: { borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 10, padding: 16, marginBottom: 16, fontSize: 16, backgroundColor: '#f9f9f9', }, button: { backgroundColor: '#007AFF', padding: 16, borderRadius: 10, alignItems: 'center', marginTop: 8, }, buttonDisabled: { backgroundColor: '#99c9ff', }, buttonText: { color: '#ffffff', fontSize: 18, fontWeight: '600', }, }); ``` > **État global vs local** > > Pour des applications plus complexes avec beaucoup d'état partagé, des solutions comme Zustand ou Redux Toolkit peuvent être préférables. Le Context est optimal pour l'état qui change rarement (thème, authentification). ## Appels API et gestion des données La communication avec un backend est centrale dans toute application mobile. Voici un pattern robuste utilisant une couche d'abstraction pour les appels API. ```tsx // services/api.ts // Configuration de base de l'API const API_BASE_URL = 'https://api.example.com'; // Type pour les erreurs API interface ApiError { message: string; code: string; status: number; } // Fonction utilitaire pour les requêtes async function request( endpoint: string, options: RequestInit = {} ): Promise { const url = `${API_BASE_URL}${endpoint}`; const config: RequestInit = { ...options, headers: { 'Content-Type': 'application/json', ...options.headers, }, }; try { const response = await fetch(url, config); if (!response.ok) { const error: ApiError = await response.json(); throw new Error(error.message || 'Une erreur est survenue'); } return response.json(); } catch (error) { // Gestion des erreurs réseau if (error instanceof TypeError) { throw new Error('Problème de connexion réseau'); } throw error; } } // Types pour les entités interface Product { id: string; name: string; price: number; description: string; imageUrl: string; } // Service produits export const productApi = { // Récupérer tous les produits getAll: () => request('/products'), // Récupérer un produit par ID getById: (id: string) => request(`/products/${id}`), // Créer un produit create: (data: Omit) => request('/products', { method: 'POST', body: JSON.stringify(data), }), // Mettre à jour un produit update: (id: string, data: Partial) => request(`/products/${id}`, { method: 'PATCH', body: JSON.stringify(data), }), }; ``` ```tsx // hooks/useProducts.ts import { useState, useEffect, useCallback } from 'react'; import { productApi } from '../services/api'; interface Product { id: string; name: string; price: number; description: string; imageUrl: string; } // Hook personnalisé pour la gestion des produits export function useProducts() { const [products, setProducts] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); // Chargement initial des produits const fetchProducts = useCallback(async () => { setIsLoading(true); setError(null); try { const data = await productApi.getAll(); setProducts(data); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur inconnue'); } finally { setIsLoading(false); } }, []); // Rafraîchissement (pull-to-refresh) const refresh = useCallback(async () => { await fetchProducts(); }, [fetchProducts]); // Chargement au montage useEffect(() => { fetchProducts(); }, [fetchProducts]); return { products, isLoading, error, refresh, }; } ``` ```tsx // screens/ProductListScreen.tsx import React from 'react'; import { View, Text, FlatList, Image, StyleSheet, RefreshControl, ActivityIndicator, } from 'react-native'; import { useProducts } from '../hooks/useProducts'; export function ProductListScreen() { const { products, isLoading, error, refresh } = useProducts(); // Affichage pendant le chargement initial if (isLoading && products.length === 0) { return ( Chargement... ); } // Affichage en cas d'erreur if (error) { return ( {error} ); } return ( item.id} contentContainerStyle={styles.list} // Pull-to-refresh refreshControl={ } renderItem={({ item }) => ( {item.name} {item.price.toFixed(2)} € )} /> ); } const styles = StyleSheet.create({ centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, loadingText: { marginTop: 12, fontSize: 16, color: '#666666', }, errorText: { fontSize: 16, color: '#FF3B30', textAlign: 'center', }, list: { padding: 16, }, card: { backgroundColor: '#ffffff', borderRadius: 12, marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, image: { width: '100%', height: 200, borderTopLeftRadius: 12, borderTopRightRadius: 12, }, cardContent: { padding: 16, }, productName: { fontSize: 18, fontWeight: '600', color: '#1a1a1a', marginBottom: 4, }, productPrice: { fontSize: 16, color: '#007AFF', fontWeight: '500', }, }); ``` ## Stockage local des données Le stockage local permet de persister des données entre les sessions. AsyncStorage est la solution standard pour les données simples, tandis que SQLite convient aux données structurées complexes. ```tsx // services/storage.ts import AsyncStorage from '@react-native-async-storage/async-storage'; // Installation requise : npx expo install @react-native-async-storage/async-storage // Clés de stockage centralisées const STORAGE_KEYS = { USER_TOKEN: '@app/user_token', USER_PREFERENCES: '@app/user_preferences', ONBOARDING_COMPLETE: '@app/onboarding_complete', } as const; // Types pour les préférences utilisateur interface UserPreferences { theme: 'light' | 'dark' | 'system'; notifications: boolean; language: string; } // Service de stockage typé export const storage = { // Token d'authentification async getToken(): Promise { return AsyncStorage.getItem(STORAGE_KEYS.USER_TOKEN); }, async setToken(token: string): Promise { await AsyncStorage.setItem(STORAGE_KEYS.USER_TOKEN, token); }, async removeToken(): Promise { await AsyncStorage.removeItem(STORAGE_KEYS.USER_TOKEN); }, // Préférences utilisateur (objet JSON) async getPreferences(): Promise { const data = await AsyncStorage.getItem(STORAGE_KEYS.USER_PREFERENCES); return data ? JSON.parse(data) : null; }, async setPreferences(prefs: UserPreferences): Promise { await AsyncStorage.setItem( STORAGE_KEYS.USER_PREFERENCES, JSON.stringify(prefs) ); }, // Onboarding async isOnboardingComplete(): Promise { const value = await AsyncStorage.getItem(STORAGE_KEYS.ONBOARDING_COMPLETE); return value === 'true'; }, async setOnboardingComplete(): Promise { await AsyncStorage.setItem(STORAGE_KEYS.ONBOARDING_COMPLETE, 'true'); }, // Nettoyage complet async clearAll(): Promise { const keys = Object.values(STORAGE_KEYS); await AsyncStorage.multiRemove(keys); }, }; ``` > **Sécurité des données sensibles** > > Pour les données sensibles comme les tokens d'authentification, utiliser expo-secure-store qui chiffre les données via le Keychain iOS et Keystore Android. ## Styles responsifs et thème Une application professionnelle doit s'adapter aux différentes tailles d'écran et supporter le mode sombre. ```tsx // theme/index.ts import { Dimensions, PixelRatio, Platform } from 'react-native'; const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window'); // Dimensions de référence (iPhone 14) const guidelineBaseWidth = 390; const guidelineBaseHeight = 844; // Fonctions de scaling export const scale = (size: number) => (SCREEN_WIDTH / guidelineBaseWidth) * size; export const verticalScale = (size: number) => (SCREEN_HEIGHT / guidelineBaseHeight) * size; export const moderateScale = (size: number, factor = 0.5) => size + (scale(size) - size) * factor; // Thème clair export const lightTheme = { colors: { primary: '#007AFF', secondary: '#5856D6', success: '#34C759', warning: '#FF9500', error: '#FF3B30', background: '#FFFFFF', surface: '#F2F2F7', text: '#000000', textSecondary: '#8E8E93', border: '#E5E5EA', }, spacing: { xs: scale(4), sm: scale(8), md: scale(16), lg: scale(24), xl: scale(32), }, typography: { h1: { fontSize: moderateScale(32), fontWeight: 'bold' as const, lineHeight: moderateScale(40), }, h2: { fontSize: moderateScale(24), fontWeight: 'bold' as const, lineHeight: moderateScale(32), }, body: { fontSize: moderateScale(16), lineHeight: moderateScale(24), }, caption: { fontSize: moderateScale(14), lineHeight: moderateScale(20), }, }, borderRadius: { sm: scale(4), md: scale(8), lg: scale(12), full: 9999, }, }; // Thème sombre export const darkTheme = { ...lightTheme, colors: { ...lightTheme.colors, background: '#000000', surface: '#1C1C1E', text: '#FFFFFF', textSecondary: '#8E8E93', border: '#38383A', }, }; export type Theme = typeof lightTheme; ``` ```tsx // context/ThemeContext.tsx import React, { createContext, useContext, useState, useEffect } from 'react'; import { useColorScheme } from 'react-native'; import { lightTheme, darkTheme, Theme } from '../theme'; import { storage } from '../services/storage'; interface ThemeContextType { theme: Theme; isDark: boolean; toggleTheme: () => void; setThemeMode: (mode: 'light' | 'dark' | 'system') => void; } const ThemeContext = createContext(undefined); export function ThemeProvider({ children }: { children: React.ReactNode }) { const systemColorScheme = useColorScheme(); const [themeMode, setThemeMode] = useState<'light' | 'dark' | 'system'>('system'); // Déterminer le thème effectif const isDark = themeMode === 'system' ? systemColorScheme === 'dark' : themeMode === 'dark'; const theme = isDark ? darkTheme : lightTheme; // Charger les préférences au démarrage useEffect(() => { storage.getPreferences().then((prefs) => { if (prefs?.theme) { setThemeMode(prefs.theme); } }); }, []); // Basculer entre clair et sombre const toggleTheme = () => { const newMode = isDark ? 'light' : 'dark'; setThemeMode(newMode); storage.getPreferences().then((prefs) => { storage.setPreferences({ ...prefs, theme: newMode, notifications: prefs?.notifications ?? true, language: prefs?.language ?? 'fr', }); }); }; return ( {children} ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; } ``` ## Préparation à la publication Avant de publier une application, plusieurs étapes de configuration et d'optimisation sont nécessaires. ```json // app.json { "expo": { "name": "MonApp", "slug": "mon-app", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "automatic", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#007AFF" }, "ios": { "supportsTablet": true, "bundleIdentifier": "com.example.monapp", "buildNumber": "1" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#007AFF" }, "package": "com.example.monapp", "versionCode": 1 }, "plugins": [ "expo-router" ] } } ``` ```bash # build.sh # Configuration EAS Build (Expo Application Services) npm install -g eas-cli # Connexion au compte Expo eas login # Configuration du projet eas build:configure # Build pour iOS (simulateur) eas build --platform ios --profile development # Build pour Android (APK de test) eas build --platform android --profile preview # Build de production eas build --platform all --profile production # Soumission aux stores eas submit --platform ios eas submit --platform android ``` > **Certificats et clés** > > La publication iOS nécessite un compte Apple Developer (99€/an) et Android un compte Google Play Console (25$ unique). EAS gère automatiquement les certificats de signature. ## Conclusion React Native offre une approche efficace pour développer des applications mobiles multiplateformes. Les fondamentaux couverts dans ce guide permettent de construire des applications complètes et professionnelles. ### Checklist pour une app React Native réussie - ✅ Configurer l'environnement avec Expo pour un démarrage rapide - ✅ Maîtriser les composants de base : View, Text, TouchableOpacity, FlatList - ✅ Implémenter la navigation avec React Navigation et le typage TypeScript - ✅ Gérer l'état avec Context API et hooks personnalisés - ✅ Structurer les appels API avec une couche de services dédiée - ✅ Persister les données localement avec AsyncStorage - ✅ Créer un système de thème responsive et support du mode sombre La documentation officielle de React Native et Expo reste la référence pour approfondir chaque sujet. L'écosystème évolue rapidement, et des bibliothèques comme React Query ou Zustand peuvent simplifier davantage la gestion des données dans les applications plus complexes. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/react-native/react-native-creer-app-mobile-complete