Les performances React sont souvent optimisées prématurément — ou pas du tout. Les développeurs juniors appliquent useMemo et useCallback partout par précaution, ou ignorent les problèmes de performance jusqu’à ce que l’application rame. Ce guide vous donne une méthode pour mesurer d’abord, puis optimiser au bon endroit.
La règle d’or : mesurer avant d’optimiser
React est rapide par défaut. Un composant qui se re-rend inutilement n’est pas forcément un problème si le rendu est rapide. Avant d’ajouter useMemo ou React.memo, profilez avec React DevTools Profiler pour identifier les composants qui causent réellement des problèmes de performance.
Ce que vous cherchez dans le Profiler : les composants avec des temps de rendu élevés (> 16ms) ou qui se re-rendent très fréquemment sans raison.
React.memo — éviter les re-renders inutiles
Par défaut, quand un composant parent se re-rend, tous ses enfants se re-rendent aussi — même si leurs props n’ont pas changé. React.memo mémoïse un composant et le re-rend uniquement si ses props changent.
// Sans memo : se re-rend à chaque render du parent
function ExpensiveChild({ data }: { data: Data }) {
return {/* calcul lourd */}
}
// Avec memo : se re-rend seulement si data change
const ExpensiveChild = React.memo(function ExpensiveChild({ data }: { data: Data }) {
return {/* calcul lourd */}
})
// ⚠️ Piège : si data est un objet créé inline dans le parent,
// la référence change à chaque render → memo ne sert à rien
function Parent() {
// ❌ Nouvel objet à chaque render → ExpensiveChild se re-rend toujours
return
// ✅ Mémoïser l'objet dans le parent
const data = useMemo(() => ({ value: 42 }), [])
return
}
useMemo — mémoïser des calculs coûteux
// Utile quand : calcul coûteux qui dépend de données qui changent rarement
const sortedAndFilteredItems = useMemo(() => {
return items
.filter(item => item.category === selectedCategory)
.sort((a, b) => a.price - b.price)
}, [items, selectedCategory])
// Inutile quand : le calcul est trivial
// ❌ Sur-utilisation — le coût de useMemo dépasse le bénéfice
const doubledValue = useMemo(() => value * 2, [value])
// ✅ Juste
const doubledValue = value * 2
useCallback — mémoïser des fonctions
// ❌ Nouvelle référence à chaque render — React.memo sur l'enfant ne fonctionne pas
function Parent() {
const handleClick = () => doSomething(userId) // nouvelle fonction à chaque render
return
}
// ✅ Référence stable — MemoizedChild ne se re-rend pas si userId n'a pas changé
function Parent() {
const handleClick = useCallback(() => doSomething(userId), [userId])
return
}
// useCallback vaut la peine quand :
// 1. La fonction est passée à un composant mémoïsé (React.memo)
// 2. La fonction est une dépendance d'un useEffect
// Dans les autres cas, c'est souvent inutile
Virtualisation — les longues listes
Le vrai problème de performance avec les listes : rendre 10 000 éléments dans le DOM, même vides. La virtualisation ne rend que les éléments visibles à l’écran.
// TanStack Virtual (anciennement react-virtual)
import { useVirtualizer } from '@tanstack/react-virtual'
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // hauteur estimée d'un élément
})
return (
{virtualizer.getVirtualItems().map(virtualItem => (
{items[virtualItem.index].name}
))}
)
}
Le lazy loading des composants
// Charger un composant lourd seulement quand il est nécessaire
const HeavyChart = lazy(() => import('./HeavyChart'))
function Dashboard() {
const [showChart, setShowChart] = useState(false)
return (
{showChart && (
Chargement... }>
)}