Afficher de longues listes de données, c'est probablement le défi le plus courant en React Native. Et aussi l'un des plus frustrants, soyons honnêtes. Un fil de discussion avec 2 000 messages, un catalogue produit de 10 000 articles, un feed social infini… dès que le nombre d'éléments grimpe, les symptômes ne se font pas attendre. Défilement saccadé, cellules blanches qui clignotent, chutes de FPS sur les appareils Android d'entrée de gamme. Si vous avez bossé sur une app React Native, vous avez forcément vécu ça.
En 2026, trois solutions se disputent la première place : FlatList (le composant intégré qu'on connaît tous), FlashList v2 (la réécriture complète de Shopify pensée pour la New Architecture), et LegendList (le petit nouveau, 100 % TypeScript). J'ai passé pas mal de temps à tester les trois sur un projet réel, et ce guide vous livre le résultat — architecture, API, performances, code concret — pour vous aider à faire le bon choix.
Pourquoi les listes posent problème en React Native
Avant de plonger dans les solutions, un petit rappel sur le problème de fond.
React Native exécute le JavaScript sur un thread séparé du thread UI natif. Quand vous faites défiler une liste, le moteur JS doit calculer quels éléments afficher, créer les composants React correspondants, et envoyer les instructions de rendu au thread natif — le tout en moins de 16 ms par frame pour maintenir 60 FPS. Autant dire que c'est serré.
Avec une liste de 10 000 éléments, il est évidemment impossible de tout rendre en même temps. C'est là qu'intervient la virtualisation : seuls les éléments visibles (plus un tampon au-dessus et en dessous) sont montés dans l'arbre de composants. Les éléments hors écran sont détruits ou recyclés.
La vraie différence entre nos trois candidats ? C'est dans la manière dont ils gèrent cette virtualisation que tout se joue.
FlatList : le composant intégré de React Native
Architecture et fonctionnement
FlatList est construit au-dessus de VirtualizedList. Son approche est simple : rendre les éléments visibles dans une fenêtre glissante, détruire les composants qui sortent de cette fenêtre, et en créer de nouveaux quand de nouveaux éléments entrent dans le viewport. Bref, c'est du « créer et détruire » en boucle.
Le souci ? Créer et détruire des composants React, c'est coûteux. À chaque nouveau composant, React doit allouer de la mémoire, exécuter les hooks d'initialisation, calculer le layout, envoyer les instructions de rendu au thread natif… Quand vous scrollez vite, le moteur JS ne suit tout simplement pas. D'où les fameuses cellules blanches.
Les props d'optimisation essentielles
Malgré ses limites, FlatList reste tout à fait utilisable pour des listes courtes — à condition de bien configurer ses props :
import React, { useCallback } from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';
const ITEM_HEIGHT = 72;
const MonItem = React.memo(({ item }) => (
{item.title}
{item.description}
));
export function ListeOptimisee({ data }) {
const renderItem = useCallback(({ item }) => (
), []);
const getItemLayout = useCallback((data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
}), []);
const keyExtractor = useCallback((item) => item.id, []);
return (
);
}
const styles = StyleSheet.create({
item: { height: ITEM_HEIGHT, padding: 16, justifyContent: 'center' },
title: { fontSize: 16, fontWeight: '600' },
subtitle: { fontSize: 14, color: '#666', marginTop: 4 },
});
Décortiquons les props clés :
getItemLayout— Si vos éléments ont une hauteur fixe, c'est de loin l'optimisation la plus impactante. Elle élimine complètement le calcul asynchrone des dimensions et supprime les cellules blanches lors du scroll rapide. Honnêtement, beaucoup de devs oublient cette prop et se plaignent ensuite des performances.maxToRenderPerBatch— Contrôle le nombre d'éléments rendus par lot. Plus c'est élevé, moins vous verrez de zones blanches… mais plus le thread JS sera chargé. Je recommande entre 3 et 8 selon la complexité de vos éléments.windowSize— Définit le nombre de viewports rendus au-dessus et en dessous de la zone visible (21 par défaut). Réduire cette valeur économise de la mémoire mais augmente le risque de cellules blanches.removeClippedSubviews— Libère les ressources des vues hors écran. Activé par défaut sur Android, mais attention, ça peut occasionnellement causer des bugs de contenu manquant (rare, mais ça arrive).React.memo()— Indispensable. Point. Sans mémoïsation, chaque élément se re-rend à chaque mise à jour de la liste, même si ses props n'ont pas changé.
Quand FlatList suffit
FlatList reste un choix parfaitement valable pour les listes de moins de 300–500 éléments avec des composants simples. Zéro dépendance supplémentaire, API stable et bien documentée, support complet de toutes les architectures. Si votre liste rentre dans ces critères, ne vous compliquez pas la vie.
FlashList v2 : la réécriture complète de Shopify
L'approche par recyclage de cellules
FlashList adopte une philosophie radicalement différente : au lieu de détruire et recréer les composants, il les recycle. Quand un élément sort de l'écran, son composant React n'est pas détruit — il est placé dans un pool de recyclage. Quand un nouvel élément entre dans le viewport, FlashList réutilise un composant existant du pool et met à jour ses props.
C'est exactement le même principe que UITableView sur iOS ou RecyclerView sur Android. Des patterns éprouvés par des décennies d'expérience mobile — et ça se sent tout de suite en termes de fluidité.
FlashList v2, sorti en 2025 et stabilisé avec React Native 0.84, est une réécriture complète de la v1. Voici les changements majeurs :
- 100 % JavaScript — Plus aucune dépendance native. La v1 utilisait RecyclerListView sous le capot ; la v2 est entièrement en JS. L'installation est devenue nettement plus simple.
- Plus besoin d'estimations de taille — En v1, il fallait fournir
estimatedItemSize(et trouver la bonne valeur était parfois pénible). En v2, FlashList mesure automatiquement. - New Architecture uniquement — FlashList v2 est conçu exclusivement pour Fabric et les TurboModules. Si vous êtes encore sur l'ancienne architecture, restez sur la v1.
- Layout Masonry intégré — Plus besoin d'importer
MasonryFlashListséparément. Une simple propmasonrysuffit, et c'est franchement plus propre. maintainVisibleContentPositionactivé par défaut — Fini les glitches visuels quand de nouveaux éléments sont ajoutés en haut de la liste. Si vous développez une app de chat, vous allez apprécier.
Installation et utilisation de base
npm install @shopify/flash-list@^2.0.0
# ou
yarn add @shopify/flash-list@^2.0.0
L'API est quasiment identique à FlatList, ce qui rend la migration vraiment simple :
import React, { useCallback } from 'react';
import { FlashList } from '@shopify/flash-list';
import { Text, View, StyleSheet } from 'react-native';
const MonItem = React.memo(({ item }) => (
{item.title}
{item.prix} €
));
export function CatalogueProduits({ produits }) {
const renderItem = useCallback(({ item }) => (
), []);
return (
item.id}
/>
);
}
const styles = StyleSheet.create({
item: { padding: 16, borderBottomWidth: 1, borderBottomColor: '#eee' },
title: { fontSize: 16, fontWeight: '600' },
prix: { fontSize: 14, color: '#2196F3', marginTop: 4 },
});
Remarquez l'absence de estimatedItemSize — en v2, ce n'est plus nécessaire. FlashList se charge de mesurer les éléments tout seul.
Layout Masonry avec FlashList v2
Créer un layout style Pinterest ? C'est devenu trivial en v2 :
import { FlashList } from '@shopify/flash-list';
export function GrillePhotos({ photos }) {
return (
(
)}
numColumns={2}
masonry
optimizeItemArrangement
keyExtractor={(item) => item.id}
/>
);
}
La prop optimizeItemArrangement (activée par défaut) réorganise automatiquement les éléments pour minimiser les différences de hauteur entre les colonnes. Plutôt malin. Et si vous avez besoin d'éléments qui s'étendent sur plusieurs colonnes, overrideItemLayout est là pour ça :
{
layout.span = item.enVedette ? 2 : 1;
}}
/>
Migration de FlashList v1 vers v2
La migration est relativement simple. Voici les points à retenir :
- Supprimez
estimatedItemSize,estimatedListSizeetestimatedFirstItemOffset - Remplacez
MasonryFlashListparFlashListavec la propmasonry - Supprimez les props dépréciées :
onBlankArea,disableHorizontalListHeightMeasurement,disableAutoLayout - Dans
overrideItemLayout, ne modifiez pluslayout.size— seullayout.spanest supporté en v2 - Supprimez les props
keyexplicites dansrenderItemet utilisez le hookuseMappingHelperpour les.map()
Un point qui mérite votre attention : la mémoïsation des props est plus importante en v2 qu'en v1. La v1 était plus « sélective » sur la mise à jour des éléments, mais ce comportement était souvent perçu comme un bug. La v2 est plus prévisible, mais en contrepartie, elle attend de vous que vous mémoïsiez correctement vos callbacks.
LegendList : le challenger 100 % TypeScript
Philosophie et architecture
LegendList est le dernier entrant dans la compétition. Développé par l'équipe derrière Legend State, c'est un composant de liste haute performance écrit entièrement en TypeScript, sans aucune dépendance native. Son point fort, et c'est ce qui le distingue vraiment : la gestion native des éléments à tailles dynamiques sans estimation préalable.
Contrairement à FlashList, LegendList ne recycle pas les éléments par défaut. Il utilise une approche hybride — virtualisation intelligente avec mesure automatique des dimensions. Le recyclage est disponible en opt-in via la prop recycleItems. Personnellement, j'aime bien cette approche : on active le recyclage quand on en a besoin, plutôt que de devoir le contourner quand il pose problème.
Installation et utilisation
npm install @legendapp/list
# ou
yarn add @legendapp/list
L'API vous sera familière :
import React, { useCallback } from 'react';
import { LegendList } from '@legendapp/list/react-native';
import { Text, View, StyleSheet } from 'react-native';
const MessageItem = React.memo(({ item }) => (
{item.auteur}
{item.contenu}
{item.heure}
));
export function ListeMessages({ messages }) {
const renderItem = useCallback(({ item }) => (
), []);
return (
item.id}
recycleItems
estimatedItemSize={80}
/>
);
}
const styles = StyleSheet.create({
message: { padding: 12, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#ddd' },
auteur: { fontSize: 14, fontWeight: '700' },
contenu: { fontSize: 15, marginTop: 4 },
heure: { fontSize: 12, color: '#999', marginTop: 6 },
});
La prop recycleItems : le boost de performance
Par défaut, LegendList ne recycle pas les composants. Si vous venez de FlashList et cherchez des performances comparables, activez recycleItems. Quelques trucs à garder en tête :
- Quand le recyclage est activé, l'état local des composants est réutilisé entre les éléments. Concrètement, gérez votre état via les props, pas via
useStateinterne — sinon vous aurez des surprises. - Utilisez
getItemTypepour catégoriser vos éléments. Les éléments du même type sont recyclés ensemble, ce qui booste les performances quand votre liste mélange des types visuellement différents.
Éléments à taille fixe : performances maximales
Si tous vos éléments ont la même hauteur, utilisez getFixedItemSize :
item.id}
recycleItems
getFixedItemSize={() => 72}
/>
Cette prop court-circuite le calcul dynamique des dimensions. C'est le chemin le plus rapide possible, ni plus ni moins.
Comparatif détaillé : FlatList vs FlashList v2 vs LegendList
Bon, on a fait le tour de chaque solution. Passons au comparatif qui fâche :
| Critère | FlatList | FlashList v2 | LegendList |
|---|---|---|---|
| Type | Intégré à RN | Package externe | Package externe |
| Stratégie | Créer / Détruire | Recyclage de cellules | Hybride (recyclage opt-in) |
| Dépendances natives | Non | Non (v2 est JS-only) | Non |
| Estimation de taille | Optionnelle | Non requise (v2) | Optionnelle |
| Tailles dynamiques | Oui (lent) | Oui | Oui (optimisé) |
| Layout Masonry | Non | Oui (prop masonry) | Non |
| New Architecture | Compatible | Requis (v2) | Compatible |
| Performances (500+ items) | Faibles | Excellentes (~10x FlatList) | Très bonnes |
| Performances (10 000+ items) | Très faibles | Excellentes | Excellentes |
| FPS sur Android entrée de gamme | 30–45 FPS | 55–60 FPS | 55–60 FPS |
| Mémoire | Élevée | Faible | Faible |
| Maturité | Très mature | Production-ready | Bêta avancée |
Guide de décision : quelle solution choisir
Choisissez FlatList si…
- Votre liste contient moins de 300 éléments
- Vos éléments sont simples (texte, icônes basiques)
- Vous ne voulez aucune dépendance externe
- Vous êtes encore sur l'ancienne architecture React Native
Choisissez FlashList v2 si…
- Vous avez des listes de 500+ éléments en production
- Vous avez besoin d'un layout Masonry
- Vous êtes sur la New Architecture (React Native 0.83+)
- Vous cherchez la solution la plus battle-tested — FlashList propulse des milliers de listes dans l'app Shopify
- Vous voulez un drop-in replacement quasi immédiat depuis FlatList
Choisissez LegendList si…
- Vos éléments ont des tailles très variables et imprévisibles
- Vous avez des mises à jour fréquentes (chat en temps réel, flux de données)
- Vous voulez un contrôle fin sur le recyclage — opt-in plutôt qu'opt-out
- Vous utilisez déjà Legend State dans votre projet
Optimisations communes aux trois solutions
Quelle que soit la solution que vous retenez, ces bonnes pratiques s'appliquent systématiquement. Et croyez-moi, elles font une vraie différence.
1. Mémoïsez vos composants d'élément
// Toujours envelopper avec React.memo
const MonElement = React.memo(({ item }) => (
{item.titre}
));
// Toujours utiliser useCallback pour renderItem
const renderItem = useCallback(({ item }) => (
), []);
2. Utilisez des clés stables
// Bien : ID stable provenant de la base de données
keyExtractor={(item) => item.id}
// Mal : index comme clé — provoque des re-rendus inutiles
keyExtractor={(item, index) => index.toString()}
J'ai vu tellement de projets utiliser l'index comme clé « parce que ça marche ». Oui, ça marche… jusqu'au moment où ça ne marche plus et que votre liste affiche n'importe quoi après un tri ou une suppression.
3. Évitez les fonctions inline
// Mal : recréée à chaque rendu
} />
// Bien : référence stable grâce à useCallback
const renderItem = useCallback(({ item }) => (
), []);
4. Testez en mode production
Celui-ci est crucial. Les builds de développement React Native sont 2 à 5 fois plus lents que les builds de production (bundles non minifiés, validation runtime, surcharges du débogueur…). Ne tirez jamais de conclusions sur les performances en mode dev.
# iOS : build de production
npx react-native run-ios --mode Release
# Android : build de production
npx react-native run-android --mode release
# Expo : build avec EAS
eas build --profile production --platform all
5. Activez Hermes V1
Avec React Native 0.84, Hermes V1 est le moteur JavaScript par défaut. Les chiffres parlent d'eux-mêmes : réduction de 29 % du temps de démarrage, 38 % de mémoire en moins, et un ramasse-miettes concurrent qui réduit les pauses GC de 73 %. Si vous n'êtes pas encore sur la 0.84, c'est honnêtement la première mise à niveau à faire avant même de vous soucier de votre composant de liste.
Exemple concret : migration d'un catalogue produit
Prenons un cas réaliste : un catalogue e-commerce avec 5 000 produits. Voici à quoi ressemble concrètement la migration de FlatList vers FlashList v2.
Avant (FlatList)
import { FlatList } from 'react-native';
export function Catalogue({ produits }) {
return (
}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => ({
length: 120,
offset: 120 * index,
index,
})}
maxToRenderPerBatch={5}
windowSize={11}
/>
);
}
Après (FlashList v2)
import { FlashList } from '@shopify/flash-list';
export function Catalogue({ produits }) {
return (
}
keyExtractor={(item) => item.id}
/>
);
}
C'est tout. Sérieusement. Moins de configuration, de meilleures performances. FlashList v2 gère automatiquement le dimensionnement, le batching et le recyclage. Sur un appareil Android milieu de gamme, cette migration a fait passer le défilement de ~35 FPS à ~58 FPS pour 5 000 éléments dans mon cas. Pas mal pour un changement de trois lignes.
FAQ
FlashList v2 fonctionne-t-il avec l'ancienne architecture React Native ?
Non. FlashList v2 est conçu exclusivement pour la New Architecture (Fabric + TurboModules). Si vous êtes encore sur l'ancienne architecture, utilisez FlashList v1.x qui reste disponible et maintenu. Cela dit, avec React Native 0.84 et Expo SDK 55, l'ancienne architecture n'est plus supportée — la migration est de toute façon inévitable à ce stade.
Quelle est la différence entre FlashList v2 et LegendList pour les listes de chat ?
Les deux gèrent bien ce cas d'usage, mais avec des approches différentes. FlashList v2 active maintainVisibleContentPosition par défaut, ce qui est idéal pour ajouter de nouveaux messages sans décalage visuel. LegendList excelle quand les messages ont des tailles très variables (texte court, images, fichiers) grâce à sa gestion native des dimensions dynamiques.
Mon conseil : pour un chat classique, partez sur FlashList v2. Pour un chat complexe avec beaucoup de types de contenu différents (messages, images, vidéos, liens enrichis), testez LegendList — la différence pourrait vous surprendre.
Comment mesurer les performances de ma liste React Native ?
Utilisez le React DevTools Profiler pour repérer les re-rendus inutiles, et Flipper ou le React Native Performance Monitor (Ctrl+M / Cmd+D → « Show Perf Monitor ») pour surveiller les FPS en temps réel. Et je ne le répéterai jamais assez : testez toujours sur un build de production, sur un appareil physique de milieu de gamme. Un émulateur en mode debug vous donnera des résultats complètement trompeurs.
Peut-on utiliser FlashList v2 avec Expo ?
Oui, sans problème. FlashList v2 est compatible avec Expo SDK 55+ (qui utilise la New Architecture par défaut). L'installation se fait avec npx expo install @shopify/flash-list. Aucune configuration native supplémentaire — c'est du 100 % JavaScript.
FlatList va-t-il être déprécié par React Native ?
Il n'y a aucune annonce de dépréciation de FlatList. C'est toujours le composant de liste officiel et intégré au framework. Cela dit, pour les cas d'usage exigeants en performances, la communauté (et même l'équipe Expo) recommande d'utiliser FlashList ou LegendList. FlatList n'est pas mort, mais il n'est clairement plus le meilleur choix pour les longues listes.