# 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