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.

Développement d'application mobile avec React Native

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.

App.tsxtsx
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
    <SafeAreaView style={styles.container}>
      {/* StatusBar configure l'apparence de la barre système */}
      <StatusBar barStyle="dark-content" />

      {/* View est l'équivalent de div */}
      <View style={styles.header}>
        {/* Text est obligatoire pour afficher du texte */}
        <Text style={styles.title}>Bienvenue sur MonApp</Text>
        <Text style={styles.subtitle}>
          Une application React Native
        </Text>
      </View>
    </SafeAreaView>
  );
}

// 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.

components/InteractiveButton.tsxtsx
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 (
    <View style={styles.container}>
      {/* TouchableOpacity réduit l'opacité au toucher */}
      <TouchableOpacity
        style={styles.button}
        activeOpacity={0.7}
        onPress={() => setCount(c => c + 1)}
      >
        <Text style={styles.buttonText}>
          Compteur: {count}
        </Text>
      </TouchableOpacity>

      {/* Pressable offre plus de contrôle sur les états */}
      <Pressable
        style={({ pressed }) => [
          styles.button,
          styles.pressableButton,
          pressed && styles.buttonPressed,
        ]}
        onPress={() => console.log('Pressed!')}
        onLongPress={() => console.log('Long press!')}
      >
        {({ pressed }) => (
          <Text style={styles.buttonText}>
            {pressed ? 'Pressé!' : 'Appuyer ici'}
          </Text>
        )}
      </Pressable>
    </View>
  );
}

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',
  },
});

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
App.tsxtsx
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<RootStackParamList>();

export default function App() {
  return (
    // NavigationContainer gère l'état de navigation
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{
          headerStyle: { backgroundColor: '#007AFF' },
          headerTintColor: '#ffffff',
          headerTitleStyle: { fontWeight: 'bold' },
        }}
      >
        <Stack.Screen
          name="Home"
          component={HomeScreen}
          options={{ title: 'Accueil' }}
        />
        <Stack.Screen
          name="Detail"
          component={DetailScreen}
          options={({ route }) => ({
            title: route.params.title
          })}
        />
        <Stack.Screen
          name="Profile"
          component={ProfileScreen}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
screens/HomeScreen.tsxtsx
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<RootStackParamList, 'Home'>;

// 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 (
    <View style={styles.container}>
      {/* FlatList pour les listes performantes */}
      <FlatList
        data={ITEMS}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <TouchableOpacity
            style={styles.item}
            onPress={() => {
              // Navigation avec paramètres typés
              navigation.navigate('Detail', {
                itemId: item.id,
                title: item.title,
              });
            }}
          >
            <Text style={styles.itemText}>{item.title}</Text>
          </TouchableOpacity>
        )}
        ItemSeparatorComponent={() => <View style={styles.separator} />}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  item: {
    backgroundColor: '#ffffff',
    padding: 20,
  },
  itemText: {
    fontSize: 16,
    color: '#1a1a1a',
  },
  separator: {
    height: 1,
    backgroundColor: '#e0e0e0',
  },
});

Prêt à réussir tes entretiens React Native ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

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.

context/AuthContext.tsxtsx
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<void>;
  signOut: () => void;
}

// Création du contexte avec valeur par défaut
const AuthContext = createContext<AuthContextType | undefined>(undefined);

// Provider qui encapsule l'application
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(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 (
    <AuthContext.Provider value={{ user, isLoading, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
}

// 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;
}
screens/LoginScreen.tsxtsx
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 (
    <View style={styles.container}>
      <Text style={styles.title}>Connexion</Text>

      <TextInput
        style={styles.input}
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        keyboardType="email-address"
        autoCapitalize="none"
        autoComplete="email"
      />

      <TextInput
        style={styles.input}
        placeholder="Mot de passe"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        autoComplete="password"
      />

      <TouchableOpacity
        style={[styles.button, isLoading && styles.buttonDisabled]}
        onPress={handleSubmit}
        disabled={isLoading}
      >
        {isLoading ? (
          <ActivityIndicator color="#ffffff" />
        ) : (
          <Text style={styles.buttonText}>Se connecter</Text>
        )}
      </TouchableOpacity>
    </View>
  );
}

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.

services/api.tstsx
// 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<T>(
  endpoint: string,
  options: RequestInit = {}
): Promise<T> {
  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<Product[]>('/products'),

  // Récupérer un produit par ID
  getById: (id: string) => request<Product>(`/products/${id}`),

  // Créer un produit
  create: (data: Omit<Product, 'id'>) =>
    request<Product>('/products', {
      method: 'POST',
      body: JSON.stringify(data),
    }),

  // Mettre à jour un produit
  update: (id: string, data: Partial<Product>) =>
    request<Product>(`/products/${id}`, {
      method: 'PATCH',
      body: JSON.stringify(data),
    }),
};
hooks/useProducts.tstsx
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<Product[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(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,
  };
}
screens/ProductListScreen.tsxtsx
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 (
      <View style={styles.centered}>
        <ActivityIndicator size="large" color="#007AFF" />
        <Text style={styles.loadingText}>Chargement...</Text>
      </View>
    );
  }

  // Affichage en cas d'erreur
  if (error) {
    return (
      <View style={styles.centered}>
        <Text style={styles.errorText}>{error}</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={products}
      keyExtractor={(item) => item.id}
      contentContainerStyle={styles.list}
      // Pull-to-refresh
      refreshControl={
        <RefreshControl
          refreshing={isLoading}
          onRefresh={refresh}
          tintColor="#007AFF"
        />
      }
      renderItem={({ item }) => (
        <View style={styles.card}>
          <Image
            source={{ uri: item.imageUrl }}
            style={styles.image}
            resizeMode="cover"
          />
          <View style={styles.cardContent}>
            <Text style={styles.productName}>{item.name}</Text>
            <Text style={styles.productPrice}>
              {item.price.toFixed(2)}            </Text>
          </View>
        </View>
      )}
    />
  );
}

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.

services/storage.tstsx
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<string | null> {
    return AsyncStorage.getItem(STORAGE_KEYS.USER_TOKEN);
  },

  async setToken(token: string): Promise<void> {
    await AsyncStorage.setItem(STORAGE_KEYS.USER_TOKEN, token);
  },

  async removeToken(): Promise<void> {
    await AsyncStorage.removeItem(STORAGE_KEYS.USER_TOKEN);
  },

  // Préférences utilisateur (objet JSON)
  async getPreferences(): Promise<UserPreferences | null> {
    const data = await AsyncStorage.getItem(STORAGE_KEYS.USER_PREFERENCES);
    return data ? JSON.parse(data) : null;
  },

  async setPreferences(prefs: UserPreferences): Promise<void> {
    await AsyncStorage.setItem(
      STORAGE_KEYS.USER_PREFERENCES,
      JSON.stringify(prefs)
    );
  },

  // Onboarding
  async isOnboardingComplete(): Promise<boolean> {
    const value = await AsyncStorage.getItem(STORAGE_KEYS.ONBOARDING_COMPLETE);
    return value === 'true';
  },

  async setOnboardingComplete(): Promise<void> {
    await AsyncStorage.setItem(STORAGE_KEYS.ONBOARDING_COMPLETE, 'true');
  },

  // Nettoyage complet
  async clearAll(): Promise<void> {
    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.

theme/index.tstsx
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;
context/ThemeContext.tsxtsx
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<ThemeContextType | undefined>(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 (
    <ThemeContext.Provider value={{ theme, isDark, toggleTheme, setThemeMode }}>
      {children}
    </ThemeContext.Provider>
  );
}

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.

app.jsonjson
{
  "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

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

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.

Tags

#react native
#mobile development
#javascript
#ios
#android

Partager

Articles similaires