Introduction
Ce guide explique les concepts avancés de React Native avec des exemples pratiques que vous pouvez exécuter localement. Nous nous concentrons sur la Nouvelle Architecture (Fabric, TurboModules, JSI), le threading et la performance, l'UI native, les flux de données, les paiements (Google Play Billing et Stripe), et l'intégration avec Expo. Vous apprendrez comment ces pièces s'articulent, quand elles sont pertinentes et comment les adopter sereinement en production.
Vue d'ensemble du workflow
Un workflow d'ingénierie clair et reproductible rend les sujets avancés gérables :
- Clarifier les objectifs
- Plateformes : Android, iOS, Expo
- Budgets : temps de démarrage, mémoire, FPS, taille du bundle
- Contraintes : paiements, support hors ligne, appareils d'entrée de gamme
- Choisir l'architecture
- Activer Hermes sur toutes les nouvelles apps
- Privilégier la Nouvelle Architecture quand les dépendances la supportent
- Implémenter les modules
- Déplacer les chemins natifs critiques ou bavards vers TurboModules ou JSI
- Construire l'UI sur Fabric au besoin
- Créer des vues natives uniquement quand les primitives React sont insuffisantes
- Optimiser les flux de données
- Concevoir les appels REST avec annulation, mise en cache et support hors ligne
- Ajouter les paiements
- Isoler Google Play Billing et Stripe dans des modules natifs
- Valider les achats côté serveur
- Tester localement
- Mesurer le démarrage, les FPS, la mémoire et la récupération d'erreurs
- Déployer progressivement
- Utiliser des feature flags et surveiller la diversité des appareils
Plongée dans l'architecture React Native
Pièces clés :
- Hermes : un moteur JavaScript compact optimisé pour le mobile, améliorant le démarrage et la mémoire
- JSI (JavaScript Interface) : une interface C++ permettant à JavaScript d'appeler du code natif sans la surcharge de sérialisation JSON
- TurboModules : le système de modules utilisant JSI pour des appels de méthodes plus rapides et le chargement paresseux
- Fabric : le nouveau renderer qui synchronise les mises à jour UI avec un moteur de layout concurrent (Yoga) et améliore la cohérence entre plateformes
Quand adopter :
- Activer Hermes sur toutes les nouvelles apps
- Utiliser TurboModules quand les appels JS-vers-natif sont fréquents ou intensifs en calcul
- Construire des composants Fabric pour de l'UI personnalisée haute performance
Exemple minimal de fonction JSI (Android C++)
Exposer un helper natif rapide via JSI pour un chemin critique (par exemple, une simple opération sur chaîne).
// cpp/JSIStringUtil.cpp
#include <jsi/jsi.h>
#include <string>
#include <cctype>
using namespace facebook;
void installStringUtil(jsi::Runtime &rt) {
auto toUpper = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "toUpperFast\)),
1,
[](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
if (count < 1 || !args[0].isString()) {
throw jsi::JSError(rt, "toUpperFast requires a string");
}
auto s = args[0].asString(rt).utf8(rt);
for (auto &c : s) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
return jsi::String::createFromUtf8(rt, s);
}
);
rt.global().setProperty(rt, "toUpperFast", std::move(toUpper));
}
// android/src/main/java/com/example/MyPackage.java
public class MyPackage implements ReactNativeHost.ReactInstanceEventListener {
@Override
public void onReactContextInitialized(ReactContext context) {
System.loadLibrary("jsi_stringutil");
ReactApplicationContext rac = (ReactApplicationContext) context;
rac.runOnNativeModulesQueueThread(() -> {
com.example.StringUtilInstaller.install(rac);
});
}
}
// Utilisation JS
const out = globalThis.toUpperFast("hello");
Utilisez ce patron seulement quand un profilage réel montre que JS est le goulot d'étranglement.
Threading, rendu et performance
Threads :
- Le thread JS exécute la logique applicative
- Le thread UI gère les mises à jour de vue et les entrées
- Fabric peut utiliser un thread de rendu pour monter les mises à jour
- TurboModules peuvent utiliser des pools d'arrière-plan pour le travail natif
Lignes directrices :
- Garder le travail sur le thread JS minimal ; découper les longues boucles avec
setImmediateourequestAnimationFrame - Regrouper les mises à jour d'état et éviter les re-rendus inutiles (mémoïser les enfants coûteux)
- Déléguer le travail lourd au natif via JSI ou des tâches d'arrière-plan
- Éviter les bridges synchrones et les gros payloads JSON
Astuces de rendu :
- Aplatir les hiérarchies de vues
- Utiliser
FlatListavecgetItemLayoutapproprié quand possible - Éviter de créer de nouveaux objets/tableaux dans les props au rendu ; mémoïser les styles
- Privilégier
Animatedou les animations natives pour les cas complexes
Helper rapide :
// Mise à jour d'état débouncée pour réduire les re-rendus
function useDebounced<T>(value: T, ms = 150) {
const [v, setV] = React.useState(value);
React.useEffect(() => {
const t = setTimeout(() => setV(value), ms);
return () => clearTimeout(t);
}, [value, ms]);
return v;
}
Modules natifs et composants UI
Plan TurboModule :
- Définir une spec TypeScript pour l'API de votre module
- Implémenter le code natif par plateforme
- Utiliser codegen pour produire des liaisons typées
Exemple de spec (infos appareil) :
// DeviceSpec.ts
export interface Spec {
getFreeMemory(): Promise<number>;
}
Esquisse d'implémentation Android :
class DeviceModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
override fun getName() = "DeviceModule"
@ReactMethod
fun getFreeMemory(promise: Promise) {
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val mi = ActivityManager.MemoryInfo()
am.getMemoryInfo(mi)
promise.resolve(mi.availMem.toDouble())
}
}
Plan vue Fabric (ProgressBar) :
- Props :
value(0..100),color - Le natif dessine une barre simple et émet
onCompleteà 100 - Valider les props et contraindre les valeurs hors plage
Sécurité :
- Valider les entrées dans le code natif
- Garder les méthodes idempotentes
- Émettre des erreurs descriptives pour les cas récupérables
Données, réseau et hors ligne
Annulation et mise en cache :
// fetchWithCache.ts
const memCache = new Map<string, any>();
export async function fetchWithCache(
url: string,
opts?: RequestInit & { cacheKey?: string; signal?: AbortSignal }
) {
const key = opts?.cacheKey ?? url;
const cached = memCache.get(key);
if (cached) {
// Servir le cache d'abord, puis rafraîchir en arrière-plan
setTimeout(() => refresh(url, key).catch(() => {}), 0);
return cached;
}
return refresh(url, key, opts?.signal);
}
async function refresh(url: string, key: string, signal?: AbortSignal) {
const res = await fetch(url, { signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
memCache.set(key, json);
return json;
}
File de mutations hors ligne :
import AsyncStorage from "@react-native-async-storage/async-storage\);\
import NetInfo from "@react-native-community/netinfo\);
type Job = { id: string; url: string; body: any; method: string };\
const KEY = "offlineQueue\);\
export async function enqueue(job: Job) {\
const raw = (await AsyncStorage.getItem(KEY)) || "[]\);
const arr: Job[] = JSON.parse(raw);\
arr.push(job);\
await AsyncStorage.setItem(KEY, JSON.stringify(arr));\
}\
\
export async function flush() {\
const state = await NetInfo.fetch();\
if (!state.isConnected) return;\
const raw = (await AsyncStorage.getItem(KEY)) || "[]\);
const arr: Job[] = JSON.parse(raw);\
const next: Job[] = [];\
for (const j of arr) {\
try {\
const res = await fetch(j.url, {\
method: j.method,\
body: JSON.stringify(j.body),\
headers: { "Content-Type": "application/json" }\
});\
if (!res.ok) throw new Error(String(res.status));\
} catch (e) {\
next.push(j); // garder pour retry\
}\
}\
await AsyncStorage.setItem(KEY, JSON.stringify(next));\
}\
Conseils serveur :
- Endpoints idempotents pour tolérer les retentatives
- Tokens à durée de vie courte ; rotation régulière
Exemples de paiements
Flux Google Play Billing (Android) :
- Initialiser
BillingClientune fois au démarrage de l'app - Interroger les détails produits avec
queryProductDetailsAsync - Lancer le flux d'achat sur le thread UI via
launchBillingFlow - Recevoir le token d'achat dans
onPurchasesUpdated; acquitter ou consommer - Valider le token d'achat sur votre serveur avant d'accorder l'entitlement
Flux Stripe :
- Utiliser les SDK natifs pour présenter
PaymentSheet(iOS) ouPaymentSheetvia le SDK Stripe Android - Ne jamais manipuler de données de carte brutes en JS
- Créer un
PaymentIntentcôté serveur ; confirmer avec le client secret dans l'app
API de module partagé (esquisse) :
// Payments.ts
export interface PaymentsSpec {
isReady(): Promise<boolean>;
listProducts(skus: string[]): Promise<{ sku: string; price: string }[]>;
startPurchase(sku: string): Promise<{ success: boolean; token?: string; error?: string }>;
presentPaymentSheet(clientSecret: string): Promise<{ success: boolean; error?: string }>;
}
Gestion d'erreurs :
- Distinguer les annulations utilisateur des échecs
- Réessayer les erreurs réseau transitoires avec backoff exponentiel
- Logger les états d'achat pour audit et réconciliation
Expo et la Nouvelle Architecture
- Hermes est activé par défaut dans les SDK Expo modernes (SDK 48+)
- Utiliser
expo prebuildquand vous avez besoin de modules natifs personnalisés (billing, Stripe) - Maintenir les versions natives alignées avec votre runtime version Expo
- Pour les paiements, prévoir prebuild ou config plugins pour inclure les SDK natifs
Plan de pilote local
Objectif : valider Hermes, un TurboModule, une vue Fabric, l'annulation REST et une interface paiements sûre.
Étapes :
- Activer Hermes et mesurer le cold start sur un Android milieu de gamme (ex. Snapdragon 765G)
- Ajouter un TurboModule exposant
getFreeMemory()et l'afficher dans un écran - Implémenter une
ProgressBarFabric avec propsvalueetcolor; marquer complet à 100 - Construire un wrapper
fetchWithCacheavecAbortController; annuler les requêtes à la navigation - Créer une interface
PaymentsModuleet utiliser des réponses mock en debug ; cacher la vraie UI derrière un feature flag - Ajouter des error boundaries et boutons de retry pour les appels réseau
- Définir les métriques de succès : démarrage < 2s, 60 fps fluides sur scroll liste, requête annulée ne met jamais à jour l'état après unmount, flux paiements mock fonctionne bout en bout
- Rédiger un script de test manuel court et l'exécuter sur simulateurs Android/iOS et au moins un appareil Android physique
Critères de sortie :
- Toutes les métriques atteintes
- Aucune promesse rejetée non gérée
- Logs montrant init et teardown propres sans fuites
Conclusion
Vous avez vu comment Hermes, JSI (JavaScript Interface), TurboModules et Fabric s'emboîtent, comment raisonner sur les threads et le rendu, et comment structurer données et paiements sereinement. Commencez par le pilote, mesurez les gains réels, puis étendez module par module. Gardez les changements derrière des flags, validez sur appareils de toutes gammes, et documentez les contrats entre JS et natif pour que les équipes évoluent en confiance.