React Native pur, Expo ou Flutter : choisir la stack mobile adaptée à votre équipe
Une comparaison pratique de React Native pur, d’Expo et de Flutter : à quoi ressemble chaque outil en termes de code, où se situent leurs faiblesses, ainsi qu’un cadre de cinq questions pour choisir parmi eux.
Choisir une stack mobile multiplateforme était autrefois une question de compromis acceptable. Aujourd’hui, React Native, Expo et Flutter permettent tous de créer des applications qui ont l’air natives, fonctionnent sans problème et peuvent s’adapter à un public très large, de sorte que la vitesse brute décide rarement du choix. Ce qui compte, c’est l’adéquation : les compétences déjà possédées par votre équipe, le code dont vous disposez déjà, et la rapidité avec laquelle vous souhaitez mettre vos applications en vente. Cet article considère Expo comme un choix à part entière et non comme une simple alternative à React Native, présente en détail l’utilisation pratique de chacune des options, et se termine par une série de questions simples pour vous aider à en choisir une.
Qu’avez-vous changé dans ces trois stacks ?
Plusieurs faiblesses structurelles qui influençaient les comparaisons antérieures ont été corrigées :
- La nouvelle architecture de React Native est désormais la par défaut. Elle s’éloigne du pont asynchrone obsolète, permettant ainsi au JavaScript et au code natif de communiquer plus directement. Les appels vers les modules natifs deviennent par conséquent beaucoup moins coûteux en ressources.
- Le rendeur Impeller de Flutter a remplacé Skia en tant que solution par défaut sur les appareils mobiles. Impeller compile ses shaders à l’avance, ce qui élimine les ralentissements causés par la compilation des shaders lors de la première exécution et assure des taux de rafraîchissement plus stables.
- Expo est devenu la méthode standard pour développer des applications React Native. Il ne s’agit plus d’un environnement d’apprentissage destiné aux débutants, mais d’une chaîne de développement professionnelle utilisée par de grandes entreprises.
Les benchmarks publiés montrent généralement que Flutter est légèrement supérieur en termes de performance de rendu dans les interfaces riches en animations, tandis que React Native l’emporte en ce qui concerne le temps de démarrage, la consommation mémoire et les opérations I/O natives. Il convient d’aborder avec prudence tous les chiffres spécifiques, car ils varient considérablement en fonction de l’application et du dispositif. Pour la plupart des produits, cet écart n’a plus d’impact sur le résultat final. Si vous souhaitez connaître le point de vue des équipes ayant expérimenté ces choix depuis un certain temps, notre article sur les choix entre Flutter et React Native qui ne se révèlent qu’en production aborde les aspects à long terme.
React Native pur : contrôle maximal, responsabilités maximales
React Native vous permet d’écrire l’interface en JavaScript ou TypeScript avec React, tandis que le framework affiche des composants natifs sur iOS et Android. Il n’y a ni WebView ni canevas personnalisé : un View devient une vue native et un Text devient un composant de texte natif.
Le compteur ci-dessous montre la forme de base d’une application selon la Nouvelle Architecture avec le rendeur Fabric. L’état est géré par l’hook useState, le bouton est un élément Pressable, et les styles sont déclarés une seule fois à l’aide de StyleSheet.create afin qu’ils puissent être validés et réutilisés.
// App.tsx — React Native (New Architecture, Fabric)
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function App() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.counter}>{count}</Text>
<Pressable style={styles.button} onPress={() => setCount(c => c + 1)}>
<Text style={styles.buttonText}>Tap me</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
counter: { fontSize: 48, fontWeight: '700', marginBottom: 24 },
button: { backgroundColor: '#2563eb', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 12 },
buttonText: { color: 'white', fontSize: 16, fontWeight: '600' },
});
Rien dans cet extrait n’est spécifique à la Nouvelle Architecture ; le même code de composant fonctionne sur les deux. Le changement d’architecture se produit en arrière-plan, dans la manière dont le rendeur et les modules natifs communiquent avec JavaScript.
Avantages
- Widgets natifs réels. Vous disposez des contrôles propres à la plateforme, de son comportement d’accessibilité et de sa mise en page du texte, sans approximation.
- Le plus grand vivier de talents. Les compétences en JavaScript, TypeScript et React sont bien plus répandues que celles nécessaires pour les autres options.
- Accès complet au niveau natif. Vous contrôlez les répertoires
ios/etandroid/, ce qui vous permet d’utiliser Swift, Kotlin, Objective-C ou Java chaque fois que c’est nécessaire. - Un écosystème immense. npm propose plus de paquets tiers que pub.dev, et la plupart des SDK mobiles pour les paiements, les cartes et l’analyse incluent des wrappers officiels pour React Native.
- Appels au niveau natif plus rapides. Fabric, TurboModules et JSI éliminent le goulot d’étranglement du vieux pont asynchrone, de sorte que les appels au code natif sont presque immédiats.
Faiblesses
- Vous êtes responsable des outils de compilation natifs. Les incohérences de version entre Xcode, Gradle et CocoaPods ainsi que les conflits de dépendances natives restent une source fréquente de problèmes.
- L’uniformité multiplateforme exige des efforts. Comme le framework est conçu pour mapper vers des widgets natifs, iOS et Android auront un aspect différent à moins de concevoir en vue de l’uniformité.
- L’infrastructure de déploiement relève de votre responsabilité. La mise en place de systèmes CI/CD, de la signature des codes et de mises à jour sans connexion est complexe à créer de zéro, c’est précisément là que Expo comble le vide.
- Modules de tiers inégaux. La qualité de maintenance des modules natifs varie du très bon au complètement abandonné.
Conseils pratiques
- Évitez de créer une nouvelle application en React Native pur sauf si vous avez une raison concrète, comme un SDK natif très spécifique ou une application native existante que vous migrez progressivement. Commencez par Expo et générez des projets natifs uniquement lorsque c’est nécessaire.
- Utilisez
react-native-reanimatedetreact-native-gesture-handlerpour les interactions sensibles à la performance. Ils exécutent les animations et les gestes sur le thread UI, ce qui évite que des threads JavaScript occupés ne provoquent des perte de frames. - Gardez Hermes activé ; c’est le moteur par défaut. Il compile à l’avance le JavaScript en bytecode, ce qui réduit considérablement le temps de démarrage et l’utilisation mémoire par rapport à JavaScriptCore.
Expo : React Native avec l’infrastructure incluse
Expo est un framework ainsi qu’un ensemble de services construits sur React Native. Son image passée était celle d’un environnement restreint ne permettant pas d’accéder au code natif. Cela n’est plus le cas : grâce à la préconstruction, également appelée génération native continue (CNG), Expo fonctionne avec l’ensemble de l’écosystème des modules natifs.
L’écran ci-dessous utilise Expo Router, qui associe les fichiers du répertoire app/ aux routes de la même manière que Next.js le fait pour le web. app/index.tsx correspond à la route d’accueil, et router.push('/profile') permet de naviguer vers le fichier qui définit /profile. Les liens profonds et les URL web suivent la même structure.
// app/index.tsx — Expo Router (file-based routing)
import { View, Text, Pressable } from 'react-native';
import { useRouter } from 'expo-router';
export default function HomeScreen() {
const router = useRouter();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16 }}>
<Text style={{ fontSize: 24, fontWeight: '600' }}>Welcome</Text>
<Pressable onPress={() => router.push('/profile')}>
<Text style={{ color: '#2563eb' }}>Go to Profile</Text>
</Pressable>
</View>
);
}
Créer et lancer un projet nécessite trois commandes. La première crée la structure de base de l’application, tandis que npx expo start démarre le serveur de développement afin de pouvoir ouvrir l’application sur un appareil ou un simulateur.
# Spin up a new project in under a minute
npx create-expo-app@latest my-app
cd my-app
npx expo start
Points forts
- Aucun outil natif pour commencer. Pour la majeure partie du développement, vous n’avez pas besoin de Xcode ou d’Android Studio ; vous pouvez tester sur un appareil physique avec Expo Go ou un client de développement personnalisé.
- EAS Build. Les builds pour iOS et Android s’exécutent dans le cloud depuis n’importe quel système d’exploitation, ce qui vous permet de générer un build iOS depuis Windows ou Linux.
- EAS Update. Les mises à jour sans connexion transmettent immédiatement les modifications de JavaScript et des ressources aux utilisateurs, sans attendre une révision en magasin, à condition qu’il n’y ait pas de modifications dans le code natif.
- Expo Router. Un routage basé sur des fichiers avec des liens profonds et une navigation universelle pour le web et les applications natives est disponible dès la première utilisation.
expo-camera, expo-notifications, expo-location et expo-image sont intégrés, documentés et mis à jour en même temps afin de fonctionner ensemble.Info.plist et AndroidManifest.xml de manière déclarative depuis app.json au lieu de les modifier manuellement, ce qui permet de garantir la reproductibilité des builds CI.npx expo prebuild génère les dossiers natifs chaque fois que vous avez besoin d’un contrôle natif complet.Faiblesses
- Les SDK spécialisés nécessitent des efforts supplémentaires. Quelques SDK natifs spécialisés exigent encore que vous écriviez votre propre plugin de configuration ou module natif, ce qui représente un travail légèrement plus important que dans React Native pur, bien que l’écart continue de se réduire.
- Le piège d’Expo Go. Une dépendance excessive envers Expo Go peut cacher le fait qu’un module natif personnalisé ne fonctionnera pas avant que vous n’ayez créé une version de développement. Cela trompe régulièrement les nouveaux utilisateurs.
- Les coûts de service. EAS Build et Update offrent des versions gratuites, mais les équipes sérieuses en environnement de production passent généralement à des forfaits payants, un coût que React Native pur avec un CI auto-hébergé permet d’éviter.
- Les limites héritées. En tant que couche par-dessus React Native, Expo conserve ses inconvénients : divergences dans l’interface selon la plateforme et un thread JavaScript qui peut devenir un goulot d’étranglement en cas de calculs intensifs.
Conseils pratiques
- Exécutez
npx expo prebuildlorsque vous avez besoin d’un module natif non pris en charge par Expo. Cela crée les dossiersios/etandroid/sur demande, de sorte que le code natif est toujours accessible. Notre article intitulé traiter les dossiers natifs comme sortie de compilation avec Expo prebuild et CNG explique en détail ce processus. - Utilisez EAS Update pour les correctifs urgents, et non pour ajouter des fonctionnalités qui modifient le comportement natif. Les règles des stores d’applications restreignent ce que le code téléchargé peut modifier, et déployer de nouvelles fonctionnalités sous couvert d’une mise à jour OTA comporte le risque de rejet ; consultez les politiques actuelles d’Apple et de Google avant de vous en servir.
- Adoptez Expo Router dès le début d’un nouveau projet. Modifier un système de navigation existant pour y intégrer un routage basé sur des fichiers est très complexe.
expo doctor avant chaque build de publication. Il détecte les incohérences de dépendances et de versions qui, sinon, se manifesteraient par des échecs de compilation natifs difficiles à interpréter.Flutter : contrôle total de chaque pixel
Flutter emprunte une approche fondamentalement différente. Au lieu de se référer à des widgets de plateforme, il dessine l’ensemble de l’interface lui-même à l’aide de son moteur Impeller, et le code Dart est compilé à l’avance en code machine natif ARM ou x86.
Le compteur Dart ci-dessous correspond à l’exemple de React Native. MyApp enrobe l’application dans MaterialApp, CounterScreen est une StatefulWidget dont la classe d’état contient _count, et en appuyant sur le bouton, on appelle setState, ce qui indique à Flutter de reconstruire cette sous-arborescence avec la nouvelle valeur.
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: CounterScreen());
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_count', style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Tap me'),
),
],
),
),
);
}
}
Le flux de travail en ligne de commande couvre l’ensemble du cycle de vie : création du projet, exécution avec chargement en temps réel pendant le développement, ainsi que génération d’artefacts de publication pour Google Play (un paquet d’application) et l’App Store (un fichier IPA).
flutter create my_app
cd my_app
flutter run # hot reload in under a second
flutter build appbundle --release # Android
flutter build ipa --release # iOS
Points forts
- UI identique partout. Comme Flutter rend chaque widget lui-même, le comportement et l’apparence sont identiques sur iOS, Android, le web et les ordinateurs de bureau, sans particularités liées à la plateforme.
- Prestations d’animation exceptionnelles. Grâce à des shaders compilés à l’avance et à un accès direct au GPU via Impeller, Flutter excelle dans les comparaisons de taux de rafraîchissement pour des interfaces complexes et riches en animations.
- Six cibles à partir d’une seule base de code. iOS, Android, le web, Windows, macOS et Linux sont tous compilés à partir du même code Dart.
Faiblesses
- L’écosystème de Dart est plus restreint. Il est plus difficile de recruter des développeurs que pour JavaScript ou TypeScript, et même les développeurs expérimentés ont généralement besoin de quelques semaines pour devenir productifs.
- Il peut paraître légèrement non naturel. Comme il ne utilise pas de widgets de plateforme, les utilisateurs attentifs peuvent remarquer de petites différences de comportement, bien que celles-ci aient considérablement diminué.
- Binaires plus volumineux. Le moteur Flutter est inclus dans chaque application, ce qui fait que les fichiers d’installation sont généralement plus gros que ceux d’une application React Native équivalente.
- pub.dev est bon, mais moins riche que npm, en particulier pour les wrappers autour de SDK natifs spécialisés.
- Aucune réutilisation du code React web. Une organisation disposant d’une base de code React web établie ne peut rien réutiliser sur le web.
Conseils pratiques
- Activez
flutter analyzeavec des règles de lint strictes dès le premier jour. La sécurité contre les valeurs nulles en Dart représente un véritable avantage, mais uniquement si vous évitez de la compromettre en utilisant constamment des typesdynamic. - Choisissez une approche de gestion d’état telle que Riverpod ou Bloc pour tout projet dépassant le stade de prototype ;
setStateseul ne permet pas de développer une application réelle.
Cinq questions pour choisir
Résolvez-les dans l’ordre. La première question à laquelle on trouve une réponse claire décide généralement du choix.
- Votre équipe travaille-t-elle déjà avec React et JavaScript ? Si oui, restez dans l’écosystème React Native et utilisez Expo par défaut. Sinon, si vous avez la liberté de choisir, Flutter et Expo sont tous deux des options solides ; choisissez le langage que votre équipe préférerait apprendre.
- Avez-vous besoin d’un design identique au pixel près ou d’animations personnalisées complexes, comme dans les jeux, les outils créatifs ou les applications axées sur la visualisation ? Flutter est le choix par défaut plus adapté.
- Souhaitez-vous partager du code ou des composants avec une application web React existante ? React Native, grâce à React Native Web, présente un réel avantage ; Flutter doit être reconstruit pour fonctionner sur le web.
- Avez-vous besoin de déployer des correctifs en JavaScript uniquement sans passer par une révision en magasin, ou de créer des applications iOS sans ordinateur Mac ? EAS Update et EAS Build d’Expo répondent directement à ces besoins.
- Intégrez-vous des écrans multiplateformes dans une grande application native existante ? Le React Native pur, ou la fonctionnalité d’ajout dans une application de Flutter, conviennent mieux que un projet Expo entièrement nouveau.
Recommandations
- Pour une équipe React ou JavaScript qui lance une nouvelle application, choisissez Expo. Il élimine la plupart des difficultés historiques liées à React Native en matière d’outils natifs, de CI/CD et de mises à jour OTA, tout en conservant toute la puissance native lorsque c’est nécessaire.
- Choisissez Flutter lorsque la qualité de l’interface, les performances des animations et la compatibilité sur mobile, ordinateur et web sont plus importantes que la réutilisation de l’écosystème JavaScript, ou lorsque votre équipe n’a pas de préférence linguistique claire et commence à zéro.
- Réservez React Native pur aux situations où il est impératif de gérer directement les projets natifs, généralement en raison d’une grande base de code natif existante ou de besoins d’intégration native inhabituels.
En résumé
Ces trois solutions se sont suffisamment rapprochées en termes de performances et de maturité pour que les capacités du framework ne soient plus rarement un goulot d’étranglement. Les facteurs décisifs sont les membres de votre équipe, le code que vous possédez déjà et la rapidité avec laquelle vous devez livrer votre application. Considérez Expo comme le moyen par défaut d’utiliser React Native, réservez React Native pur aux cas où il est essentiel de gérer soi-même les projets natifs, et choisissez Flutter lorsque le contrôle de la rendu et la portée multiplateforme l’emportent sur les avantages d’utiliser JavaScript. Quelle que soit votre choix, validez la partie la plus risquée de votre application — qu’il s’agisse d’un SDK natif, d’une animation complexe ou de la version web — dès les premières semaines plutôt qu’au moment du lancement.