Introduction
Ce guide explique des React Native advanced concepts avec des exemples pratiques que vous pouvez exécuter localement. Nous nous concentrons sur la New Architecture (Fabric, TurboModules, JSI), la gestion des threads et des performances, la UI native, les flux de données, les paiements (Google Play Billing et Stripe) et l'intégration avec Expo. Vous comprendrez comment ces briques fonctionnent en interne (React Native internals), quand elles importent, et comment les adopter en production en toute sécurité. Ce React Native deep dive inclut des React Native examples concrets et reproductibles.
Vue d'ensemble du workflow
Un workflow d'ingénierie clair et répétable garde les sujets avancés maîtrisables :
- Clarifier les objectifs
- Plateformes : Android, iOS, Expo.
- Budgets : démarrage, mémoire, FPS, taille du bundle.
- Contraintes : paiements, hors‑ligne, appareils d'entrée de gamme.
- Choisir l'architecture
- Activer Hermes.
- Préférer la New Architecture quand les dépendances la supportent.
- Implémenter des modules
- Déplacer les chemins natifs chauds ou bavards vers des TurboModules ou du JSI.
- Construire la UI en Fabric si nécessaire
- Créer des vues natives seulement quand les primitives ne suffisent pas.
- Optimiser les flux de données
- Concevoir les appels REST avec annulation, cache et hors‑ligne.
- Ajouter les paiements
- Isoler Google Play Billing et Stripe dans des modules natifs.
- Valider les achats côté serveur.
- Tester localement
- Mesurer démarrage, FPS, mémoire et reprise après erreur.
- Déployer progressivement
- Utiliser des feature flags et surveiller la diversité des appareils.
React Native architecture deep dive
Pièces clés :
- Hermes : un moteur JavaScript léger optimisé pour le mobile, améliorant le temps de démarrage et l'utilisation mémoire.
- JSI : une interface C++ permettant à JS d'appeler du code natif sans la surcharge de sérialisation JSON.
- TurboModules : le système de modules basé sur JSI pour des appels plus rapides et un chargement paresseux.
- Fabric : le nouveau renderer qui synchronise les mises à jour de l'UI avec le moteur de layout concurrent (Yoga) et améliore la cohérence entre plateformes.
Quand adopter :
- Activer Hermes sur toutes les nouvelles apps.
- Utiliser des TurboModules lorsque les appels JS‑vers‑natif sont fréquents ou intensifs en calcul.
- Construire des composants Fabric pour une UI personnalisée et hautes performances.
Exemple minimal JSI (Android C++)
Exposez un utilitaire natif rapide via JSI pour un chemin chaud (par exemple, une simple opération sur chaîne). Utilisez ce motif uniquement lorsqu'un profil montre que JS est le goulot d'étranglement.
// cpp/JSIStringUtil.cpp
#include <jsi/jsi.h>
#include <string>
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 = (char)toupper((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) {
// Charger la bibliothèque C++ puis installer la liaison JSI.
System.loadLibrary("jsi_stringutil");
ReactApplicationContext rac = (ReactApplicationContext) context;
rac.runOnNativeModulesQueueThread(() -> {
com.example.StringUtilInstaller.install(rac);
});
}
}
// Utilisation JS
const out = globalThis.toUpperFast("hello");
Threads, rendu et performance
Threads :
- Le thread JS exécute la logique d'application.
- Le thread UI gère les mises à jour de vues et les entrées.
- Fabric peut utiliser un thread de rendu pour monter les updates.
- Les TurboModules peuvent employer des pools d'arrière‑plan pour le travail natif.
Lignes directrices :
- Gardez le travail sur le thread JS réduit ; découpez les longues boucles avec setImmediate ou requestAnimationFrame.
- Batching des mises à jour d'état et évitez les re‑rendus inutiles (mémoïsez les enfants coûteux).
- Déportez le lourd vers le natif via JSI ou des tâches en arrière‑plan.
- Évitez les ponts synchrones et les gros payloads JSON.
Astuces de rendu :
- Aplatissez les hiérarchies de vues.
- Utilisez FlatList avec un getItemLayout approprié lorsque possible.
- Évitez de créer de nouveaux objets/tableaux en props dans le render ; mémoïsez les styles.
- Préférez Animated ou des animations natives pour les cas complexes.
Helper rapide :
// Mise à jour d'état avec debounce pour réduire les re-renders
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 un spec TypeScript pour l'API du module.
- Implémenter le code natif par plateforme.
- Utiliser le codegen pour produire des bindings typés.
Exemple de spec (informations appareil) :
// DeviceSpec.ts
export interface Spec {
getFreeMemory(): Promise<number>;
}
Ébauche 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 de vue Fabric (ProgressBar) :
- Props : value (0..100), color.
- Le natif dessine une barre simple et émet onComplete à 100.
- Validez les props et corrigez les valeurs hors plage.
Sécurité :
- Valider les entrées côté natif.
- Garder des méthodes idempotentes.
- Émettre des erreurs descriptives pour les cas récupérables.
Données, réseau et hors‑ligne
Annulation et 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 d'attente 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); // conserver pour réessai
}
}
await AsyncStorage.setItem(KEY, JSON.stringify(next));
}
Conseils serveur :
- Endpoints idempotents pour tolérer les réessais.
- Jetons de courte durée ; faites tourner régulièrement.
Exemples de paiements
Flux Google Play Billing (Android) :
- Initialiser un BillingClient une seule fois.
- Récupérer les détails des produits.
- Lancer l'achat sur le thread UI.
- Recevoir le token d'achat, accuser réception (acknowledge) ou consommer.
- Valider côté serveur.
Flux Stripe :
- Utiliser les SDKs natifs pour présenter PaymentSheet.
- Ne jamais manipuler de données de carte brutes en JS.
- Créer le PaymentIntent côté serveur ; confirmer avec le client secret dans l'app.
Esquisse d'API de module partagé :
// 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 des erreurs :
- Distinguer les annulations utilisateur des échecs.
- Réessayer les erreurs réseau transitoires avec backoff.
- Journaliser les états d'achat pour audit et rapprochement.
Expo et la New Architecture
- Hermes est activé par défaut dans les SDKs Expo modernes.
- Utilisez prebuild lorsque vous avez besoin de modules natifs (billing, Stripe).
- Gardez les versions natives alignées avec votre runtime Expo.
- Pour les paiements, prévoyez prebuild ou des config plugins pour inclure les SDKs natifs.
Plan pilote local
Objectif : prouver Hermes, un TurboModule, une vue Fabric, l'annulation REST et une interface de paiement sûre.
Étapes :
- Activer Hermes et relever le cold start sur un Android milieu de gamme.
- Ajouter un TurboModule exposant getFreeMemory() et l'afficher dans un écran.
- Implémenter une ProgressBar Fabric avec value et color ; marquer complete à 100.
- Construire un wrapper fetchWithCache avec AbortController ; annuler les requêtes à la navigation.
- Créer une interface PaymentsModule et utiliser des réponses mock en debug ; masquer la vraie UI derrière un feature flag.
- Ajouter des error boundaries et des boutons de retry pour le réseau.
- Définir des métriques : démarrage < 2 s, 60 fps sur le scroll de liste, aucune mise à jour d'état après un unmount suite à une annulation, et un flux paiement mock bout‑en‑bout.
- Rédiger un court script de test manuel et 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 propres à l'initialisation et à l'arrêt, sans fuites.
Conclusion
Vous avez vu comment Hermes, JSI, TurboModules et Fabric s'articulent, comment raisonner sur les threads et le rendu, et comment structurer les données et les paiements en sécurité. Démarrez avec le pilote, mesurez des gains réels, puis étendez module par module. Gardez les changements derrière des flags, validez sur des appareils de différents niveaux et documentez les contrats entre JS et natif pour évoluer en confiance.