Vingt éléments dans un flux, c'est parfait. Le défilement est d'une fluidité exemplaire. Votre client est ravi. Puis vous passez en production, les données arrivent, et soudain, vous vous retrouvez face à deux mille lignes. L'interface utilisateur commence à saccader. La mémoire grimpe jusqu'à ce que le système d'exploitation coupe tout. Par désespoir, certains développeurs enveloppent tout dans un ScrollView et considèrent que le problème est réglé. Cette décision engendre généralement trois nouveaux bugs pour chaque bug résolu.

Les listes sont le goulot d'étranglement de performance qui définit la perception qu'ont les utilisateurs de votre application React Native. Si vous les gérez bien, l'application semble native. Si vous vous trompez, même l'écran le plus magnifique devient laborieux. La cause profonde est généralement un décalage entre le composant choisi et la charge de travail imposée aux threads JavaScript et UI. React Native fonctionne sur deux pistes. Votre logique réside sur le thread JS, tandis que le rendu se produit sur le thread UI natif. Lorsque vous rendez une liste massive de manière incorrecte, les deux threads commencent à se noyer dans les calculs de mise en page, les re-renders et les allocations de mémoire. Le résultat est une perte d'images, des flashs blancs et, finalement, un crash.

Choisissez le bon outil

Le choix d'un composant de liste doit être une décision architecturale délibérée, et non un réflexe.

ScrollView est l'option la plus simple. Il prend chaque enfant que vous lui donnez, monte chacun d'entre eux immédiatement en mémoire et remet l'ensemble au moteur de défilement natif. C'est exactement ce dont vous avez besoin pour du contenu court et fixe, comme un écran de paramètres, un formulaire de connexion ou une page de détails de produit statique avec dix sections. C'est prévisible et facile à styliser. Le hic, c'est qu'il n'y a pas de virtualisation. Si vous lui donnez deux mille éléments, il créera docilement deux mille vues natives. N'utilisez pas ScrollView pour des ensembles de données volumineux ou dynamiques. Considérez-le comme une affiche encadrée, pas comme une étagère de bibliothèque.

FlatList est le bourreau de travail pour les flux longs et uniformes. Il virtualise le contenu, ce qui signifie qu'il ne monte que les lignes actuellement visibles ou proches de la zone d'affichage. À mesure que l'utilisateur fait défiler, FlatList démonte les cellules qui sortent de l'écran et les recycle pour les nouvelles données. Cela permet de maintenir une utilisation stable de la mémoire, quelle que soit la taille de votre tableau. Si vous construisez un fil d'actualité social, un centre de notifications ou toute collection de cartes similaires défilant continuellement, FlatList est le choix par défaut correct.

SectionList est une version organisée de FlatList. Utilisez-la lorsque vos données arrivent par groupes, comme un carnet d'adresses trié par ordre alphabétique, un journal d'entraînement divisé par date ou une liste de factures organisée par mois. Elle affiche des en-têtes de section collants (sticky headers) et gère la logique de regroupement pour vous. Sous le capot, elle utilise le même moteur de virtualisation que FlatList, vous offrant ainsi les mêmes avantages en termes de mémoire avec la structure supplémentaire de partitions titrées.

FlashList intervient lorsque vous devez tirer chaque image possible de l'appareil. Construit sur l'écosystème RecyclerListView, il recycle les vues de manière plus agressive que FlatList et vise à maintenir soixante images par seconde, même sur du matériel de milieu de gamme. Si vous construisez une interface de chat à haut volume, un catalogue de produits avec une vitesse de défilement rapide, ou n'importe quel écran où la fluidité est un avantage concurrentiel, FlashList vaut la dépendance supplémentaire. Il n'est pas nécessaire pour chaque écran, mais pour les flux qui définissent l'expérience principale, la différence de performance est notable.

Les tueurs de performance courants

Il y a trois suspects habituels lorsqu'une liste commence à ralentir.

Monter trop d'arbres React à la fois est l'échec le plus spectaculaire. Lorsque chaque ligne est un arbre de composants complexe, le rendu initial peut bloquer le thread JS suffisamment longtemps pour produire un écran blanc vide ou un premier rendu (first paint) retardé et disgracieux. L'utilisateur ouvre l'application et attend. Même après le chargement initial, des lignes lourdes rendent l'initialisation du défilement lente car les premières images sont consommées par le travail de configuration.

Trop de travail par image se manifeste par des saccades pendant le défilement. Vous disposez d'un budget d'environ seize millisecondes par image pour maintenir des animations fluides. Si un composant de ligne effectue des calculs coûteux, analyse des dates à la volée ou effectue des comparaisons d'objets profondes à l'intérieur du render, vous dépassez ce budget. Le thread UI perd des images et l'utilisateur ressent une secousse.

L'utilisation excessive de la mémoire est le tueur silencieux. Chaque vue native coûte de la RAM. Ajoutez des images volumineuses non optimisées, des ombres portées sur chaque carte ou des éléments tactiles (touchables) imbriqués, et l'empreinte mémoire se multiplie. Sur iOS, le système peut fermer votre application sans avertissement. Sur Android, l'utilisateur voit la latence s'accumuler jusqu'à ce que l'application devienne inutilisable.

Liste de contrôle pour l'optimisation

De petites habitudes stratégiques séparent une liste qui fonctionne simplement d'une liste qui est d'une fluidité exceptionnelle.

Utilisez des clés stables. Passez toujours un véritable identifiant de votre jeu de données à la prop key. N'utilisez jamais l'index du tableau. Si votre liste est réordonnée, filtrée ou si des éléments sont ajoutés, une clé basée sur l'index trompe React en associant les mauvaises données au mauvais composant recyclé. Cette erreur déclenche des démontages (unmounts) inutiles, des incohérences d'état et des re-rendus en cascade. Un ID approprié indique précisément à React quelle ligne a été déplacée et où.

Mémoïsez les lignes. Enveloppez votre composant de ligne dans React.memo afin qu'il ne se re-rende que lorsque ses props changent réellement. Sans cette protection, toute mise à jour de l'état du parent peut déclencher un cycle de rendu sur chaque ligne visible, même si leurs données sont identiques. Dans une liste longue et dynamique, ces cycles gaspillés s'accumulent rapidement.

Gardez renderItem stable. Évitez de définir une nouvelle fonction directement à l'intérieur de la prop renderItem à chaque rendu du parent. Une fonction fléchée en ligne telle que renderItem={({ item }) => <Row data={item} />} crée une nouvelle référence à chaque mise à jour du parent. FlatList détecte un changement de prop et recycle la ligne inutilement. Définissez la fonction de rendu en dehors du composant ou mémoïsez-la avec useCallback pour que la référence reste stable.

Utilisez getItemLayout dès que possible. Si vos lignes ont une hauteur fixe ou prévisible, indiquez précisément sa valeur à FlatList. Cette prop permet à la liste d'éviter des appels de mesure natifs coûteux. Au lieu de mesurer chaque cellule après le montage (mount), la liste calcule la position mathématiquement. La différence est particulièrement marquée sur les listes contenant des centaines ou des milliers d'éléments, où le flux incessant de onLayout peut paralyser le thread JS.

Optimisez les images de manière agressive. Les images sans dimensions définies sont le poison des listes. Définissez toujours une largeur (width) et une hauteur (height) explicites afin que la couche native réserve l'espace avant le décodage de l'image. Pour les images distantes, utilisez une bibliothèque de mise en cache telle qu'Expo Image ou un équivalent qui gère la mise en cache en mémoire, la persistance sur disque et l'optimisation du format. Le composant Image par défaut de React Native convient pour les prototypes, mais les flux de production nécessitent un meilleur contrôle de la mémoire et des états de chargement.

Évitez d'imbriquer des conteneurs de défilement. Ne placez jamais une FlatList verticale à l'intérieur d'une ScrollView verticale. La ScrollView parente capture tous les événements de défilement et perturbe la capacité de la FlatList enfant à mesurer sa zone d'affichage (viewport). La virtualisation échoue car FlatList ne sait plus quelles lignes doivent être visibles. Résultat : chaque ligne est montée quoi qu'il arrive, ce qui annule tout l'intérêt de la virtualisation. Si vous avez besoin d'un en-tête au-dessus d'une liste, utilisez la prop ListHeaderComponent de FlatList. Si vous avez besoin d'un comportement de type 'sticky' complexe, utilisez une SectionList ou une FlashList avec la configuration d'en-tête appropriée.

La règle d'or

Si le contenu est restreint et fini, laissez ScrollView s'en charger. Si le contenu augmente avec des données générées par les utilisateurs ou une pagination à distance, utilisez une liste virtualisée. Lorsque la liste est l'élément central de l'application et que les utilisateurs vont défiler pendant plusieurs minutes, tournez-vous vers FlashList.

Voici une dernière vérité qu'il est facile d'oublier. Des lignes épurées défilent rapidement. Plus votre composant de ligne est léger, plus votre liste est fluide. Éliminez les navigations imbriquées, les calculs lourds et les animations superflues de chaque ligne individuelle. Gardez un markup plat, une logique légère et des images dimensionnées. Une liste vit ou meurt par le poids cumulé de ce qu'elle affiche. Rendez chaque ligne peu coûteuse en ressources, et la liste aura une sensation de fluidité haut de gamme.