## Introduction

React Native vous permet de créer des applications mobiles avec JavaScript et React, mais les applications de production exigent une base architecturale solide. Ce guide explique l'architecture de base avec des exemples pratiques : le modèle de threads, le bridge, les composants de base, le flux de données et le flux de contrôle. Vous apprendrez comment les composants communiquent, où les défaillances surviennent et comment concevoir pour la fiabilité et la performance. Nous nous concentrons sur les compromis opérationnels et les étapes de vérification, pas seulement sur la théorie.

## Inventaire des versions et de l'environnement

Avant de mettre en œuvre des décisions architecturales, établissez votre version de React Native et votre environnement. Utilisez npx react-native info pour imprimer le rapport d'environnement. Exemple de sortie :

System:
 OS: macOS 13.4
 CPU: (10) arm64 Apple M1 Max
 Memory: 32 GB
Binaries:
 Node: 18.16.0
 npm: 9.5.1
 Watchman: 2023.05.22.00
SDKs:
 iOS SDK: 16.5
 Android SDK:
 API Levels: 31, 33
 Build Tools: 30.0.3, 31.0.0, 33.0.0
IDEs:
 Android Studio: 2022.2 AI-222.4459.24.2221.10121639
 Xcode: 14.5 
 Pour un pilote minimal, choisissez une fonctionnalité à écran unique pour valider les modèles architecturaux. Documentez votre version de React Native (par exemple, 0.72.5), la version de React (par exemple, 18.2.0) et les modules natifs pertinents. Le modèle de bridge diffère selon les versions : la nouvelle architecture (Fabric/TurboModules) est optionnelle dans la version 0.72. Vérifiez que votre projet utilise l'architecture appropriée pour les modules choisis. Si vous êtes sur l'ancienne architecture, vous pouvez encore avoir le bridge ; la nouvelle architecture remplace le bridge par JavaScript Interface (JSI) pour une communication directe. Vérifiez votre package.json et les paramètres de build natifs pour confirmer.

## Chemin de configuration sûr

Un chemin de configuration sûr implique des choix d'implémentation limités. Par exemple, limitez l'exposition des modules natifs en n'enveloppant que les fonctions nécessaires. Utilisez le bridge React Native explicitement pour la communication entre JavaScript et le code natif. Dans la nouvelle architecture, les TurboModules offrent un moyen plus efficace.

Considérons un module natif simple pour les informations sur l'appareil utilisant les TurboModules (nouvelle architecture). Définissez une spécification en JavaScript :

// NativeDeviceInfo.js
import { TurboModuleRegistry, TurboModule } from 'react-native';

export interface Spec extends TurboModule {
 getDeviceName(): Promise<string>;
}

export default TurboModuleRegistry.get<Spec>('NativeDeviceInfo'); 
 Implémentez en Kotlin (Android) :

// NativeDeviceInfo.kt
class NativeDeviceInfo(reactContext: ReactApplicationContext) :
 ReactContextBaseJavaModule(reactContext) {

 override fun getName() = "NativeDeviceInfo"

 @ReactMethod
 fun getDeviceName(promise: Promise) {
 promise.resolve(android.os.Build.MODEL)
 }
} 
 Enregistrez le module dans un package. Sur Android, créez un ReactPackage et ajoutez-le à MainApplication.java ou MainApplication.kt :

class NativeDeviceInfoPackage : ReactPackage {
 override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
 return listOf(NativeDeviceInfo(reactContext))
 }

 override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
 return emptyList()
 }
} 
 Ajoutez ensuite NativeDeviceInfoPackage() à la liste getPackages() dans MainApplication . Cette approche limitée garantit que seules les capacités natives requises sont exposées, réduisant les problèmes de maintenabilité. Évitez d'ajouter des modules larges qui exposent de nombreuses fonctions sauf si nécessaire ; chaque méthode supplémentaire augmente la surface de bogues et de problèmes de versionnage.

Pour le flux de données, structurez l'état au niveau supérieur de chaque fonctionnalité et passez les props vers le bas. Utilisez React Context uniquement pour les données véritablement globales comme l'authentification. Évitez de surutiliser l'état global, ce qui complique les tests et la réutilisation. Par exemple, dans un écran de fonctionnalité, gardez l'état local dans le composant écran et passez les props aux composants enfants. Si vous avez besoin d'un état partagé entre frères, remontez l'état au parent commun le plus proche. Pour les préoccupations transversales comme le thème ou l'authentification, Context est approprié. Utilisez des bibliothèques comme Redux ou Zustand uniquement lorsque la complexité le justifie.

## Plongée dans le modèle de threads

React Native fonctionne sur trois threads principaux : le thread UI (thread principal), le thread JavaScript et le thread d'arrière-plan (pour les modules natifs). Comprendre ces threads vous aide à éviter les problèmes de performance.

- Thread UI : gère le rendu de l'interface native et les interactions utilisateur. Le bloquer provoque des gels d'application. Gardez le travail lourd hors de ce thread.

- Thread JavaScript : exécute votre code React, y compris le cycle de vie des composants et les mises à jour d'état. Dans l'ancienne architecture, ce thread communique avec le natif via le bridge. Dans la nouvelle architecture, il utilise JSI pour des appels directs.

- Thread des modules natifs : un pool de threads séparé pour les appels de modules natifs. Les tâches natives de longue durée doivent s'exécuter ici pour éviter de bloquer le thread UI. Sur Android, utilisez un ReactContextBaseJavaModule avec @ReactMethod(isBlockingSynchronousMethod = false) pour une exécution asynchrone.

Exemple : Si vous faites une requête réseau lourde dans un module natif, exécutez-la sur un thread d'arrière-plan et résolvez la promesse une fois terminée. En Kotlin :

@ReactMethod
fun fetchData(promise: Promise) {
 Thread {
 try {
 val result = performNetworkCall() // appel bloquant
 promise.resolve(result)
 } catch (e: Exception) {
 promise.reject("FETCH_ERROR", e.message, e)
 }
 }.start()
} 
 En JavaScript, vous l'appelez de manière asynchrone :

NativeModule.fetchData().then(result => {
 console.log('Data:', result);
}).catch(error => {
 console.error('Error:', error);
}); 
 Bloquer le thread JavaScript avec des calculs lourds peut également provoquer des pertes d'images. Utilisez InteractionManager pour différer le travail jusqu'après les animations :

InteractionManager.runAfterInteractions(() => {
 // Calcul lourd ou mise à jour d'état
}); 
 Ou utilisez requestAnimationFrame pour planifier le travail au début de l'image suivante.

## Architecture des composants

Les composants React Native sont les briques de votre interface utilisateur. Ils suivent le même modèle de composants que React : props, state et cycle de vie. Mais les composants natifs ont des considérations supplémentaires.

Les composants principaux incluent View , Text , Image , ScrollView et TextInput . Chacun correspond à une vue native. Des composants natifs personnalisés peuvent être créés avec le moteur de rendu Fabric de la nouvelle architecture, mais pour la plupart des applications, l'utilisation des composants intégrés avec un style approprié est suffisante.

Un arbre de composants bien conçu a une séparation claire des préoccupations. Les composants de présentation reçoivent des données via des props et appellent des callbacks pour les événements. Les composants conteneurs gèrent l'état et les effets secondaires. Ce modèle améliore la testabilité et la réutilisabilité.

Exemple : Un écran de profil utilisateur peut avoir :

- ProfileScreen (conteneur) : récupère les données utilisateur, gère l'état de chargement.

- ProfileHeader (présentation) : affiche l'avatar et le nom, appelle onEdit lorsque le bouton d'édition est pressé.

- ProfileDetails (présentation) : affiche la biographie et les statistiques.

function ProfileScreen() {
 const [user, setUser] = useState(null);
 const [loading, setLoading] = useState(true);

 useEffect(() => {
 fetchUser().then(data => {
 setUser(data);
 setLoading(false);
 });
 }, []);

 if (loading) {
 return <ActivityIndicator />;
 }

 return (
 <View>
 <ProfileHeader user={user} onEdit={() => navigation.navigate('EditProfile')} />
 <ProfileDetails user={user} />
 </View>
 );
} 

## Flux de données et gestion de l'état

 Le flux de données dans React Native suit le même modèle unidirectionnel que React : l'état descend du parent vers l'enfant via des props, et les événements remontent via des callbacks. Cela rend l'application prévisible et plus facile à déboguer.

Pour l'état local du composant, utilisez le hook useState . Pour un état plus complexe qui s'étend sur plusieurs composants, envisagez de remonter l'état. Pour l'état global, React Context est une solution intégrée. Exemple avec l'authentification :

const AuthContext = React.createContext(null);

export function AuthProvider({ children }) {
 const [user, setUser] = useState(null);

 const signIn = async (email, password) => {
 const user = await api.signIn(email, password);
 setUser(user);
 };

 const signOut = () => setUser(null);

 return (
 <AuthContext.Provider value={{ user, signIn, signOut }}>
 {children}
 </AuthContext.Provider>
 );
}

export function useAuth() {
 return useContext(AuthContext);
} 
 Enveloppez la racine de votre application avec AuthProvider , puis n'importe quel composant peut accéder à l'état d'authentification. Cependant, évitez de mettre tout l'état dans Context ; cela provoque des re-rendus pour tous les consommateurs lorsque n'importe quelle valeur change. Divisez les contextes par domaine ou utilisez des sélecteurs avec des bibliothèques comme Redux.

Pour la récupération de données asynchrones, utilisez un hook personnalisé avec useEffect et nettoyage. Exemple avec annulation :

function useFetch(url) {
 const [data, setData] = useState(null);
 const [error, setError] = useState(null);
 const [loading, setLoading] = useState(true);

 useEffect(() => {
 const controller = new AbortController();
 fetch(url, { signal: controller.signal })
 .then(res => res.json())
 .then(setData)
 .catch(err => {
 if (err.name !== 'AbortError') setError(err);
 })
 .finally(() => setLoading(false));
 return () => controller.abort();
 }, [url]);

 return { data, error, loading };
} 

## Flux de contrôle et navigation

 Le flux de contrôle dans les applications mobiles implique souvent la navigation. React Navigation est la bibliothèque standard. Elle fournit des navigateurs de pile, d'onglets et de tiroir. Décidez d'une structure de navigation tôt, car elle affecte l'organisation des composants et l'état.

Exemple de navigateur de pile avec des paramètres typés :

import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

function AppNavigator() {
 return (
 <Stack.Navigator>
 <Stack.Screen name="Home" component={HomeScreen} />
 <Stack.Screen name="Profile" component={ProfileScreen} />
 </Stack.Navigator>
 );
} 
 Utilisez les liens profonds pour gérer les liens externes. Configurez le linking dans votre conteneur de navigation :

const linking = {
 prefixes: ['myapp://'],
 config: {
 screens: {
 Home: '',
 Profile: 'profile/:id',
 },
 },
}; 
 Alors myapp://profile/123 naviguera vers l'écran Profile avec l'id 123.

Pour le flux de contrôle comme les conditionnelles, gardez-les simples et testées. Utilisez des instructions if dans le rendu, ou le rendu conditionnel avec && . Évitez les chaînes ternaires complexes ; extrayez-les dans des fonctions.

## Vérification et diagnostics

La vérification inclut les journaux d'exécution, l'inspection avec React DevTools et le profilage des performances. Assurez-vous que les journaux du bundler Metro montrent des builds réussis. Utilisez console.log en développement, mais supprimez-les en production pour éviter la surcharge. Mieux, utilisez une bibliothèque de journalisation comme react-native-logs qui peut être désactivée en production.

Exemple de diagnostic : vérifiez le nombre de messages du bridge via react-native log-android ou log-ios . Si vous utilisez la nouvelle architecture, activez la journalisation verbeuse dans le code natif pour confirmer l'enregistrement du module :

// dans App.js
if (__DEV__) {
 const NativeDeviceInfo = require('./NativeDeviceInfo').default;
 NativeDeviceInfo.getDeviceName().then(name => console.log('Device:', name));
} 
 Sortie attendue dans la console Metro :

Device: Pixel 5 
 Utilisez React Native Performance Monitor (ou Flipper) pour inspecter l'utilisation du thread JS et du thread UI. Pour le flux de données, vérifiez que les mises à jour d'état se reflètent correctement dans l'interface en testant avec React Native Testing Library. Exemple de test :

import { render, fireEvent, waitFor } from '@testing-library/react-native';
import App from './App';

test('charge et affiche l\'utilisateur', async () => {
 const { getByText } = render(<App />);
 await waitFor(() => expect(getByText('John Doe')).toBeTruthy());
}); 
 Exécutez les tests avec npm test . Pour les tests de bout en bout, utilisez Detox. Ces tests vérifient le flux de données et le flux de contrôle dans des conditions réalistes.

## Modes de défaillance et récupération

Les modes de défaillance courants incluent la congestion du bridge, les plantages de modules natifs et les échecs de chargement du bundle JS. Un problème classique est l'envoi de données volumineuses sur le bridge de manière synchrone, provoquant des gels de l'interface. Atténuez en utilisant des appels asynchrones et le traitement par lots.

Exemple : Si votre application plante en raison d'un module natif manquant, vous pouvez voir une erreur de boîte rouge :

TurboModuleRegistry.getEnforcing(...): 'NativeDeviceInfo' could not be found. Verify that a module by this name is registered in the native binary. 
 Revenez en arrière en vous assurant que le module est correctement lié : pour l'autolinking, vérifiez que le projet natif inclut le module et reconstruisez. Pour l'ancienne architecture, exécutez npx react-native link (bien que déprécié). Étapes de liaison manuelle : sur Android, assurez-vous que le package est ajouté dans MainApplication ; sur iOS, assurez-vous que le pod est installé et lié.

Vérifications de récupération : après correction, rechargez l'application et confirmez que le module se charge via la sortie console.log. Testez également sur un appareil physique pour un comportement réaliste. Surveillez les journaux pour les erreurs après le déploiement.

## Pièges courants

1. Surutilisation du bridge : De nombreux développeurs passent fréquemment de grands tableaux ou objets sur le bridge. Le bridge sérialise les données en JSON, ce qui peut être lent. Évitez en regroupant les appels, en utilisant des tableaux typés ou en déplaçant les données lourdes vers le côté natif. Pour les données à haute fréquence (comme l'accéléromètre), utilisez des émetteurs d'événements avec limitation.

Exemple : Au lieu d'envoyer 1000 mises à jour individuelles par seconde, regroupez-les toutes les 100 ms ou utilisez un module natif qui émet des événements avec la dernière valeur.

2. Fuites de mémoire dues à des écouteurs non supprimés : Lorsque vous utilisez DeviceEventEmitter ou NativeEventEmitter , supprimez toujours les écouteurs dans componentWillUnmount ou le nettoyage de useEffect . Sinon, l'écouteur garde une référence au composant, empêchant la récupération de place.

useEffect(() => {
 const subscription = DeviceEventEmitter.addListener('event', handler);
 return () => subscription.remove();
}, []); 
 3. Blocage du thread UI : Effectuer des calculs lourds sur le thread JS peut provoquer des pertes d'images. Utilisez InteractionManager ou déplacez les calculs vers des threads d'arrière-plan natifs. Ou utilisez requestAnimationFrame pour planifier le travail.

4. Éparpillement de la gestion de l'état : Utiliser trop d'état global (Redux) pour des préoccupations locales rend le code plus difficile à tester. Identifiez quel état est véritablement global (auth, thème) et gardez le reste local. Refactorisez lorsque vous voyez un forage de props plus profond que 3-4 niveaux.

5. Environnement incohérent entre développeurs : Des versions différentes de React Native ou des dépendances natives causent des problèmes de type « fonctionne sur ma machine ». Imposez un environnement cohérent avec un fichier de verrouillage, et utilisez des outils comme nvm ou n pour la gestion des versions de Node. Documentez les étapes d'installation et exécutez npx react-native info pour comparer.

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle pour assurer la santé architecturale. Attribuez un propriétaire pour chaque élément et définissez une fréquence de révision.

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Vérification</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Quoi vérifier</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Propriétaire</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Fréquence</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Cohérence de l&#39;environnement</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">npx react-native info</code> correspond à la base de référence de l&#39;équipe</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Responsable ingénierie</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Hebdomadaire</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Débit du bridge</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pas de grandes charges utiles synchrones ; utilisez des API asynchrones</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Responsable technique</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Revue de code</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Enregistrement des modules natifs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Tous les modules personnalisés apparaissent dans les journaux lorsqu&#39;ils sont utilisés</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Développeur</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Après changements</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Temps de chargement du bundle JS</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Mesurez le temps jusqu&#39;à l&#39;interactivité ; moins de 3 secondes sur l&#39;appareil cible</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur QA</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Test de version</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Utilisation de la mémoire</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Pas de fuites ; utilisez des outils de profilage</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur performance</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Avant version</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Liens profonds de navigation</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Testez les liens profonds clés fonctionnent</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Ingénieur QA</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Mensuel</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Journalisation des erreurs</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Les limites d&#39;erreur capturent les erreurs de rendu ; journaux envoyés au service</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Responsable frontend</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Revue de sprint</td></tr></tbody>
</table>
</div>
Exécutez la liste de contrôle après des changements majeurs et avant chaque version. Pour un pilote, réduisez la portée : une fonctionnalité, un module natif, un modèle de flux de données. Cette approche réduit les retouches et permet une validation mesurable. Revisitez la liste de contrôle trimestriellement pour mettre à jour les propriétaires ou les fréquences en fonction des changements d'équipe.

## Conclusion

Comprendre l'architecture de React Native est essentiel pour créer des applications mobiles maintenables et performantes. En inventoriant votre environnement, en configurant des modules natifs limités, en vérifiant par des diagnostics et en vous préparant aux défaillances, vous établissez une base solide. Commencez par un pilote étroit, comme recommandé, pour valider vos choix architecturaux avant de passer à l'échelle. Utilisez la liste de contrôle opérationnelle pour maintenir la qualité au fil du temps. Prochaines étapes : sélectionnez une petite fonctionnalité, implémentez un module natif personnalisé en utilisant l'architecture appropriée et mesurez les améliorations de performance.