RÉSUMÉ
Guide complet de l’optimisation des performances React
Maîtrisez les techniques avancées d’optimisation React 2026 pour créer des applications ultra-rapides.
Mots-clés: React.memo, useMemo, Server Components
TABLE DES MATIÈRES
1 Comprendre les performances React en 2026
2 React.memo et la mémorisation des composants
3 useMemo et useCallback : optimiser les calculs
4 Lazy Loading et Code Splitting avancé
5 React Server Components : la révolution 2026
6 Techniques avancées et outils de mesure
7 Guide pratique d’implémentation
INTRODUCTION
Comprendre les performances React en 2026
L’optimisation des performances React est devenue cruciale en 2026, avec des applications de plus en plus complexes et des attentes utilisateurs toujours plus élevées. Selon les dernières données de Web Vitals, une différence de 100ms dans le temps de chargement peut impacter le taux de conversion de 7%.

React 18 et ses évolutions en 2026 ont introduit des paradigmes révolutionnaires comme les Server Components, le Concurrent Rendering, et des améliorations significatives des hooks existants. Ces nouvelles fonctionnalités permettent d’atteindre des performances jusqu’à 40% supérieures par rapport aux versions précédentes.
POINT CLÉ
En 2026, les applications React bien optimisées affichent un First Contentful Paint (FCP) inférieur à 1.3s et un Largest Contentful Paint (LCP) sous les 2.5s, respectant ainsi les Core Web Vitals de Google.
« L’optimisation React n’est plus optionnelle, c’est une nécessité business »
— État de l’écosystème React 2026
Les enjeux de performance modernes
Les défis de performance en 2026 sont multiples. D’abord, la complexité croissante des applications avec des trees de composants pouvant atteindre plus de 1000 nœuds. Ensuite, la diversité des devices, des smartphones 5G aux montres connectées, nécessite une optimisation cross-platform.
Métriques de performance essentielles
Time to Interactive (TTI) — Temps avant que l’utilisateur puisse interagir
Cumulative Layout Shift (CLS) — Stabilité visuelle de la page
First Input Delay (FID) — Réactivité aux premières interactions
« Bundle Size Impact — Impact de la taille du bundle sur les performances
Les statistiques montrent qu’une application React mal optimisée peut consommer jusqu’à 300% de ressources supplémentaires par rapport à une version optimisée. Cela se traduit par une batterie qui se vide plus rapidement sur mobile et une expérience utilisateur dégradée.
MÉMORISATION
React.memo et la mémorisation des composants
React.memo est l’une des techniques d’optimisation les plus puissantes disponibles en 2026. Cette Higher-Order Component permet d’éviter les re-rendus inutiles en mémorisant le résultat d’un composant fonctionnel. Selon les benchmarks 2026, React.memo peut réduire les temps de rendu de 60% dans les applications complexes.

EXPLICATION DU CODE
Voici un exemple basique d’utilisation de React.memo pour optimiser un composant de liste d’articles. Sans memo, ce composant se re-rendrait à chaque changement du parent.
import React, { memo } from 'react';
// Composant non optimisé - se re-rend à chaque fois
const ArticleCard = ({ title, content, author }) => {
console.log('ArticleCard rendered');
return (
<div className="article-card">
<h3>{title}</h3>
<p>{content.substring(0, 100)}...</p>
<span>Par {author}</span>
</div>
);
};
// Version optimisée avec React.memo
const OptimizedArticleCard = memo(ArticleCard);
// Utilisation dans une liste
const ArticleList = ({ articles, filter }) => {
const filteredArticles = articles.filter(article =>
article.category === filter
);
return (
<div>
{filteredArticles.map(article => (
<OptimizedArticleCard
key={article.id}
title={article.title}
content={article.content}
author={article.author}
/>
))}
</div>
);
};Comparaison personnalisée avec React.memo
React.memo accepte une fonction de comparaison personnalisée en second paramètre. Cette fonctionnalité est particulièrement utile pour les objets complexes ou lorsque vous voulez ignorer certaines props dans la comparaison.
EXPLICATION DU CODE
Cet exemple montre comment créer une fonction de comparaison personnalisée pour optimiser un composant user qui ne doit se re-rendre que si certaines propriétés changent.
import React, { memo } from 'react';
const UserProfile = ({ user, theme, onlineStatus, lastSeen }) => {
return (
<div className={`user-profile ${theme}`}>
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
<span className={onlineStatus ? 'online' : 'offline'}>
{onlineStatus ? 'En ligne' : `Vu il y a ${lastSeen}`}
</span>
</div>
);
};
// Fonction de comparaison personnalisée
const arePropsEqual = (prevProps, nextProps) => {
// Ignore les changements de lastSeen si l'utilisateur est en ligne
if (prevProps.onlineStatus && nextProps.onlineStatus) {
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name &&
prevProps.theme === nextProps.theme &&
prevProps.onlineStatus === nextProps.onlineStatus
);
}
// Comparaison standard pour les utilisateurs offline
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name &&
prevProps.theme === nextProps.theme &&
prevProps.onlineStatus === nextProps.onlineStatus &&
prevProps.lastSeen === nextProps.lastSeen
);
};
const OptimizedUserProfile = memo(UserProfile, arePropsEqual);POINT CLÉ
Attention à ne pas surutiliser React.memo ! Il ajoute un overhead de comparaison. Utilisez-le uniquement pour les composants qui se re-rendent fréquemment avec les mêmes props.
PROBLÈME 01
React.memo ne fonctionne pas avec les objets
Un piège classique : passer un objet créé inline comme prop annule l’effet de React.memo car l’objet est recréé à chaque rendu.
SOLUTION — Stabilisez les références d’objets
// ❌ Problématique - objet recréé à chaque rendu
<OptimizedComponent
config={{ theme: 'dark', size: 'large' }}
/>
// ✅ Solution - objet stable
const config = useMemo(() => ({
theme: 'dark',
size: 'large'
}), []);
<OptimizedComponent config={config} />« React.memo peut réduire jusqu’à 60% les re-rendus inutiles dans les listes complexes »
— Benchmark React Performance 2026
HOOKS D’OPTIMISATION
useMemo et useCallback : optimiser les calculs
Les hooks useMemo et useCallback sont les piliers de l’optimisation React moderne. En 2026, ces hooks ont été améliorés avec un algorithme de cache plus intelligent qui réduit de 25% la consommation mémoire par rapport aux versions précédentes.

Maîtriser useMemo pour les calculs coûteux
useMemo permet de mémoriser le résultat d’un calcul coûteux et ne le recalcule que si ses dépendances changent. Cette technique est particulièrement efficace pour les transformations de données, les filtres complexes ou les calculs mathématiques lourds.
EXPLICATION DU CODE
Exemple d’optimisation d’un dashboard avec des calculs statistiques complexes. Sans useMemo, ces calculs seraient refaits à chaque rendu même si les données n’ont pas changé.
import React, { useMemo, useState } from 'react';
const AnalyticsDashboard = ({ salesData, period }) => {
const [selectedMetric, setSelectedMetric] = useState('revenue');
// Calculs coûteux mémorisés
const statistics = useMemo(() => {
console.log('Calcul des statistiques...');
const totalRevenue = salesData.reduce((sum, sale) => sum + sale.amount, 0);
const averageOrderValue = totalRevenue / salesData.length;
const topProducts = salesData
.reduce((products, sale) => {
products[sale.productId] = (products[sale.productId] || 0) + 1;
return products;
}, {});
const conversionRate = (salesData.length / 10000) * 100; // Simulation
return {
totalRevenue: totalRevenue.toFixed(2),
averageOrderValue: averageOrderValue.toFixed(2),
topProducts: Object.entries(topProducts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5),
conversionRate: conversionRate.toFixed(2)
};
}, [salesData]); // Recalcule seulement si salesData change
// Filtre des données selon la période
const filteredData = useMemo(() => {
const now = new Date();
const periodStart = new Date(now.setDate(now.getDate() - period));
return salesData.filter(sale =>
new Date(sale.date) >= periodStart
);
}, [salesData, period]);
// Calcul du graphique selon la métrique sélectionnée
const chartData = useMemo(() => {
console.log('Génération des données graphique...');
if (selectedMetric === 'revenue') {
return filteredData.map(sale => ({
date: sale.date,
value: sale.amount
}));
}
return filteredData.reduce((acc, sale) => {
const date = sale.date.split('T')[0];
acc[date] = (acc[date] || 0) + 1;
return acc;
}, {});
}, [filteredData, selectedMetric]);
return (
<div className="dashboard">
<div className="metrics-grid">
<div className="metric-card">
<h3>Chiffre d'affaires</h3>
<p>{statistics.totalRevenue}€</p>
</div>
<div className="metric-card">
<h3>Panier moyen</h3>
<p>{statistics.averageOrderValue}€</p>
</div>
<div className="metric-card">
<h3>Taux de conversion</h3>
<p>{statistics.conversionRate}%</p>
</div>
</div>
</div>
);
};useCallback pour stabiliser les fonctions
useCallback mémorise une fonction et ne la recrée que si ses dépendances changent. C’est essentiel pour éviter les re-rendus en cascade dans les composants enfants qui dépendent de ces fonctions comme props.
EXPLICATION DU CODE
Exemple d’une liste de tâches avec des actions optimisées. Chaque fonction de callback est mémorisée pour éviter de recréer les composants enfants.
import React, { useState, useCallback, memo } from 'react';
// Composant enfant optimisé avec memo
const TaskItem = memo(({ task, onToggle, onDelete, onEdit }) => {
console.log(`TaskItem ${task.id} rendu`);
return (
<div className="task-item">
<input
type="checkbox"
checked={task.completed}
onChange={() => onToggle(task.id)}
/>
<span className={task.completed ? 'completed' : ''}>
{task.title}
</span>
<button onClick={() => onEdit(task.id)}>Éditer</button>
<button onClick={() => onDelete(task.id)}>Supprimer</button>
</div>
);
});
const TaskList = () => {
const [tasks, setTasks] = useState([
{ id: 1, title: 'Apprendre React', completed: false },
{ id: 2, title: 'Optimiser les performances', completed: true },
{ id: 3, title: 'Déployer l\'application', completed: false }
]);
const [filter, setFilter] = useState('all');
// Fonctions callback mémorisées
const handleToggleTask = useCallback((taskId) => {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === taskId
? { ...task, completed: !task.completed }
: task
)
);
}, []); // Pas de dépendances car on utilise la forme fonctionnelle
const handleDeleteTask = useCallback((taskId) => {
setTasks(prevTasks =>
prevTasks.filter(task => task.id !== taskId)
);
}, []);
const handleEditTask = useCallback((taskId) => {
const newTitle = prompt('Nouveau titre :');
if (newTitle) {
setTasks(prevTasks =>
prevTasks.map(task =>
task.id === taskId
? { ...task, title: newTitle }
: task
)
);
}
}, []);
// Filtrage des tâches mémorisé
const filteredTasks = useMemo(() => {
switch (filter) {
case 'completed':
return tasks.filter(task => task.completed);
case 'active':
return tasks.filter(task => !task.completed);
default:
return tasks;
}
}, [tasks, filter]);
return (
<div className="task-list">
<div className="filter-buttons">
<button onClick={() => setFilter('all')}>Toutes</button>
<button onClick={() => setFilter('active')}>Actives</button>
<button onClick={() => setFilter('completed')}>Terminées</button>
</div>
{filteredTasks.map(task => (
<TaskItem
key={task.id}
task={task}
onToggle={handleToggleTask}
onDelete={handleDeleteTask}
onEdit={handleEditTask}
/>
))}
</div>
);
};POINT CLÉ
Privilégiez la forme fonctionnelle des setters (ex: setTasks(prev => ...)) dans useCallback pour éviter d’ajouter l’état dans les dépendances.
Avantages
✓ Réduction de 40-60% des recalculs inutiles
✓ Stabilisation des références pour React.memo
✓ Amélioration notable de la fluidité UI
Inconvénients
✗ Overhead de comparaison des dépendances
✗ Complexité accrue du code
✗ Risque de fuites mémoire si mal utilisé
CODE SPLITTING
Lazy Loading et Code Splitting avancé
Le lazy loading et le code splitting sont devenus incontournables en 2026. Avec des applications React moyennes pesant plus de 2MB, diviser intelligemment le code peut réduire le temps de chargement initial de 70%. React 18 propose des APIs améliorées qui rendent ces techniques plus accessibles et performantes.

Lazy Loading intelligent avec React.lazy
React.lazy permet de charger les composants uniquement quand ils sont nécessaires. En 2026, cette fonction a été optimisée avec un système de preloading prédictif qui anticipe les besoins utilisateur.
EXPLICATION DU CODE
Mise en place d’un système de routing avec lazy loading pour une application e-commerce. Chaque page n’est chargée qu’au moment de la navigation.
import React, { Suspense, lazy } from 'react';
import { Routes, Route, Link } from 'react-router-dom';
// Composants chargés de façon paresseuse
const HomePage = lazy(() => import('./pages/HomePage'));
const ProductsPage = lazy(() => import('./pages/ProductsPage'));
const CartPage = lazy(() => import('./pages/CartPage'));
const ProfilePage = lazy(() => import('./pages/ProfilePage'));
// Composant de chargement personnalisé
const LoadingSpinner = () => (
<div className="loading-container">
<div className="spinner"></div>
<p>Chargement en cours...</p>
</div>
);
// Composant de fallback d'erreur
const ErrorBoundary = ({ children }) => {
const [hasError, setHasError] = React.useState(false);
React.useEffect(() => {
const handleError = (error) => {
console.error('Erreur de chargement lazy:', error);
setHasError(true);
};
window.addEventListener('error', handleError);
return () => window.removeEventListener('error', handleError);
}, []);
if (hasError) {
return (
<div className="error-fallback">
<h2>Oops ! Une erreur s'est produite</h2>
<button onClick={() => setHasError(false)}>
Réessayer
</button>
</div>
);
}
return children;
};
const App = () => {
return (
<div className="app">
<nav className="navigation">
<Link to="/">Accueil</Link>
<Link to="/products">Produits</Link>
<Link to="/cart">Panier</Link>
<Link to="/profile">Profil</Link>
</nav>
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductsPage />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</Suspense>
</ErrorBoundary>
</div>
);
};
export default App;Preloading stratégique
Une technique avancée consiste à précharger les composants selon le comportement utilisateur. Par exemple, précharger la page panier quand l’utilisateur survole un bouton « Ajouter au panier ».
EXPLICATION DU CODE
Hook personnalisé pour le preloading intelligent des composants lazy. Il précharge automatiquement selon les interactions utilisateur.
import React, { useEffect, useCallback } from 'react';
// Hook personnalisé pour le preloading intelligent
const usePreloader = () => {
const preloadedComponents = React.useRef(new Set());
const preloadComponent = useCallback((importFunction) => {
const componentName = importFunction.toString();
if (!preloadedComponents.current.has(componentName)) {
preloadedComponents.current.add(componentName);
// Précharge le composant
importFunction().then(module => {
console.log('Composant préchargé:', module.default.name);
}).catch(error => {
console.warn('Erreur de préchargement:', error);
preloadedComponents.current.delete(componentName);
});
}
}, []);
return { preloadComponent };
};
// Composant avec preloading intelligent
const ProductCard = ({ product }) => {
const { preloadComponent } = usePreloader();
// Précharge la page produit au survol
const handleMouseEnter = useCallback(() => {
preloadComponent(() => import('./pages/ProductDetailPage'));
}, [preloadComponent]);
// Précharge le panier quand l'utilisateur s'apprête à ajouter
const handleAddToCartHover = useCallback(() => {
preloadComponent(() => import('./pages/CartPage'));
}, [preloadComponent]);
return (
<div
className="product-card"
onMouseEnter={handleMouseEnter}
>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.price}€</p>
<button
className="add-to-cart"
onMouseEnter={handleAddToCartHover}
onClick={() => {
// Logique d'ajout au panier
}}
>
Ajouter au panier
</button>
</div>
);
};
// Preloading basé sur l'intersection observer
const useIntersectionPreloader = (ref, importFunction) => {
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Précharge quand l'élément devient visible
importFunction();
observer.unobserve(element);
}
});
},
{
rootMargin: '100px' // Commence le chargement 100px avant
}
);
observer.observe(element);
return () => observer.disconnect();
}, [ref, importFunction]);
};
// Utilisation avec intersection observer
const LazySection = () => {
const sectionRef = React.useRef(null);
useIntersectionPreloader(
sectionRef,
() => import('./components/HeavyComponent')
);
return <div ref={sectionRef}>Section qui déclenche le preloading</div>;
};« Le code splitting peut réduire le bundle initial de 70% dans les grandes applications »
— État du développement React 2026
Métriques d’impact du Code Splitting
☑ Réduction du bundle initial : 60-70%
☑ Amélioration du First Contentful Paint : 45%
☑ Réduction de la consommation mémoire : 30%
☐ Setup initial plus complexe
SERVER COMPONENTS
React Server Components : la révolution 2026
Les React Server Components (RSC) représentent le changement de paradigme le plus significatif depuis l’introduction des hooks. En 2026, ils sont devenus la norme pour les applications performantes, offrant un rendu côté serveur optimisé et une expérience utilisateur sans précédent avec des temps de chargement réduits de 80%.

Comprendre les Server Components
Contrairement aux composants traditionnels qui s’exécutent dans le navigateur, les Server Components s’exécutent sur le serveur et génèrent du JSX statique. Ils permettent d’accéder directement aux bases de données, APIs, et fichiers système sans exposer ces ressources au client.
EXPLICATION DU CODE
Exemple d’un Server Component qui récupère des données directement depuis la base de données. Ce code s’exécute uniquement côté serveur.
// ServerComponents/ProductsList.server.js
import { db } from '../lib/database';
import ProductCard from './ProductCard.client';
// Server Component - s'exécute côté serveur
export default async function ProductsList({ category, limit = 10 }) {
// Accès direct à la base de données (impossible côté client)
const products = await db.products.findMany({
where: { category },
take: limit,
include: {
reviews: {
select: { rating: true },
take: 5
},
inventory: {
select: { stock: true }
}
}
});
// Calculs complexes côté serveur
const productsWithStats = products.map(product => {
const avgRating = product.reviews.length > 0
? product.reviews.reduce((sum, review) => sum + review.rating, 0) / product.reviews.length
: 0;
return {
...product,
avgRating: Math.round(avgRating * 10) / 10,
inStock: product.inventory.stock > 0,
isPopular: product.reviews.length > 50 && avgRating > 4
};
});
return (
<div className="products-grid">
<h2>{category} ({productsWithStats.length} produits)</h2>
{productsWithStats.map(product => (
<ProductCard
key={product.id}
product={product}
// Le Server Component passe les données au Client Component
/>
))}
</div>
);
}
// ServerComponents/UserDashboard.server.js
import { getUserData, getUserOrders, getUserStats } from '../lib/auth';
export default async function UserDashboard({ userId }) {
// Requêtes parallèles pour optimiser les performances
const [user, orders, stats] = await Promise.all([
getUserData(userId),
getUserOrders(userId, { limit: 10 }),
getUserStats(userId)
]);
// Traitement des données sensibles côté serveur uniquement
const secureUserData = {
name: user.name,
email: user.email.replace(/(.{2})(.*)(@.*)/, '$1***$3'),
memberSince: user.createdAt,
tier: user.subscriptionTier
};
return (
<div className="dashboard">
<WelcomeHeader user={secureUserData} />
<OrdersPreview orders={orders} />
<StatsWidget stats={stats} />
</div>
);
}Client Components interactifs
Les Client Components gèrent l’interactivité et l’état côté client. Ils travaillent en tandem avec les Server Components pour offrir une expérience utilisateur optimale.
EXPLICATION DU CODE
Client Component qui reçoit des données du Server Component et gère l’interactivité utilisateur comme l’ajout au panier ou les favoris.
'use client'; // Directive pour marquer comme Client Component
import { useState, useTransition } from 'react';
import { addToCart, addToWishlist } from '../actions/userActions';
// Client Component - s'exécute côté client
export default function ProductCard({ product }) {
const [isLoading, setIsLoading] = useState(false);
const [isPending, startTransition] = useTransition();
const [isInWishlist, setIsInWishlist] = useState(product.isInUserWishlist);
const handleAddToCart = async () => {
setIsLoading(true);
try {
// Server Action - communication avec le serveur
await addToCart(product.id, 1);
// Feedback utilisateur optimiste
toast.success(`${product.name} ajouté au panier !`);
} catch (error) {
toast.error('Erreur lors de l\'ajout au panier');
} finally {
setIsLoading(false);
}
};
const handleWishlistToggle = () => {
startTransition(async () => {
const newWishlistState = !isInWishlist;
setIsInWishlist(newWishlistState); // UI optimiste
try {
if (newWishlistState) {
await addToWishlist(product.id);
} else {
await removeFromWishlist(product.id);
}
} catch (error) {
setIsInWishlist(!newWishlistState); // Rollback en cas d'erreur
toast.error('Erreur lors de la mise à jour des favoris');
}
});
};
return (
<div className="product-card">
<div className="product-image">
<img src={product.imageUrl} alt={product.name} />
{product.isPopular && (
<span className="badge popular">Populaire</span>
)}
</div>
<div className="product-info">
<h3>{product.name}</h3>
<p className="price">{product.price}€</p>
<div className="rating">
<span className="stars">
{'★'.repeat(Math.floor(product.avgRating))}
{'☆'.repeat(5 - Math.floor(product.avgRating))}
</span>
<span className="rating-text">
{product.avgRating} ({product.reviewCount} avis)
</span>
</div>
<div className="stock-status">
{product.inStock ? (
<span className="in-stock">En stock</span>
) : (
<span className="out-of-stock">Rupture de stock</span>
)}
</div>
</div>
<div className="product-actions">
<button
onClick={handleAddToCart}
disabled={isLoading || !product.inStock}
className="add-to-cart-btn"
>
{isLoading ? 'Ajout...' : 'Ajouter au panier'}
</button>
<button
onClick={handleWishlistToggle}
disabled={isPending}
className={`wishlist-btn ${isInWishlist ? 'active' : ''}`}
>
{isInWishlist ? '❤️' : '🤍'}
</button>
</div>
</div>
);
}POINT CLÉ
Les Server Components n’ont pas d’état ni de cycles de vie. Ils génèrent du JSX statique qui est sérialisé et envoyé au client pour être intégré à l’arbre des composants.
80%
réduction du temps de chargement
Avec les Server Components optimisés
PROBLÈME 02
Gestion de l’état entre Server et Client Components
La communication entre Server et Client Components peut être complexe, notamment pour partager l’état global de l’application.
SOLUTION — Utiliser les Server Actions et le cache de React
// actions/cartActions.js
'use server';
import { revalidateTag } from 'next/cache';
export async function addToCartAction(productId, quantity) {
await db.cartItems.create({
data: { productId, quantity, userId: await getCurrentUserId() }
});
// Invalide le cache pour forcer la mise à jour
revalidateTag('user-cart');
return { success: true };
}TECHNIQUES AVANCÉES
Techniques avancées et outils de mesure
Au-delà des techniques fondamentales, l’optimisation React 2026 inclut des stratégies avancées comme le virtualization, le debouncing intelligent, et des outils de mesure sophistiqués. Ces techniques permettent d’atteindre des performances exceptionnelles même sur des applications complexes traitant des millions d’éléments.
Virtualisation pour les grandes listes
La virtualisation permet de rendre uniquement les éléments visibles dans une liste, réduisant drastiquement l’impact sur les performances. En 2026, les bibliothèques comme @tanstack/react-virtual offrent des performances 95% supérieures aux listes traditionnelles.
EXPLICATION DU CODE
Implémentation d’une liste virtualisée capable de gérer 100,000+ éléments sans impact sur les performances. Seuls les éléments visibles sont rendus dans le DOM.
import React, { useMemo } from 'react';
import { useVirtualizer } from '@tanstack/react-virtual';
const VirtualizedProductList = ({ products }) => {
const parentRef = React.useRef(null);
const virtualizer = useVirtualizer({
count: products.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 120, // Hauteur estimée d'un élément
overscan: 10, // Nombre d'éléments à pré-rendre hors vue
// Optimisation pour les éléments de taille variable
measureElement:
typeof window !== 'undefined' && navigator.userAgent.includes('Firefox')
? undefined // Firefox a des problèmes avec measureElement
: (element) => element?.getBoundingClientRect().height,
});
const items = virtualizer.getVirtualItems();
// Calcul des métriques de performance
const visibleCount = items.length;
const totalHeight = virtualizer.getTotalSize();
return (
<div className="virtualized-container">
<div className="list-stats">
<span>Total: {products.length.toLocaleString()} produits</span>
<span>Rendus: {visibleCount} éléments</span>
<span>Performance: {((visibleCount / products.length) * 100).toFixed(1)}% d'économie</span>
</div>
<div
ref={parentRef}
className="list-container"
style={{
height: '600px',
overflow: 'auto',
}}
>
<div
style={{
height: `${totalHeight}px`,
width: '100%',
position: 'relative',
}}
>
{items.map((virtualItem) => {
const product = products[virtualItem.index];
return (
<div
key={virtualItem.key}
data-index={virtualItem.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
>
<ProductItem
product={product}
index={virtualItem.index}
isVisible={true}
/>
</div>
);
})}
</div>
</div>
</div>
);
};
// Composant optimisé pour la virtualisation
const ProductItem = React.memo(({ product, index, isVisible }) => {
// Lazy loading des images seulement pour les éléments visibles
const [imageLoaded, setImageLoaded] = React.useState(!isVisible);
React.useEffect(() => {
if (isVisible && !imageLoaded) {
const img = new Image();
img.onload = () => setImageLoaded(true);
img.src = product.imageUrl;
}
}, [isVisible, imageLoaded, product.imageUrl]);
return (
<div className="product-item">
<div className="product-image">
{imageLoaded ? (
<img src={product.imageUrl} alt={product.name} />
) : (
<div className="image-placeholder">Chargement...</div>
)}
</div>
<div className="product-info">
<h3>{product.name}</h3>
<p>{product.price}€</p>
<span className="index">#{index + 1}</span>
</div>
</div>
);
});Debouncing et optimisation des recherches
L’optimisation des champs de recherche avec debouncing intelligent et cache peut réduire de 90% les appels API tout en améliorant l’expérience utilisateur.
EXPLICATION DU CODE
Hook personnalisé combinant debouncing, cache et annulation des requêtes pour optimiser les recherches en temps réel.
import React, { useState, useEffect, useRef, useMemo } from 'react';
// Hook personnalisé pour la recherche optimisée
const useOptimizedSearch = (searchFunction, options = {}) => {
const {
debounceMs = 300,
cacheSize = 100,
minQueryLength = 2,
} = options;
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const cache = useRef(new Map());
const abortControllerRef = useRef(null);
const debounceTimerRef = useRef(null);
// Nettoyage du cache selon la politique LRU
const cleanCache = useMemo(() => {
return () => {
if (cache.current.size > cacheSize) {
const firstKey = cache.current.keys().next().value;
cache.current.delete(firstKey);
}
};
}, [cacheSize]);
const performSearch = async (searchQuery) => {
// Vérification du cache
if (cache.current.has(searchQuery)) {
const cachedResult = cache.current.get(searchQuery);
setResults(cachedResult.data);
setIsLoading(false);
// Mise à jour de l'ordre LRU
cache.current.delete(searchQuery);
cache.current.set(searchQuery, {
...cachedResult,
timestamp: Date.now()
});
return;
}
// Annulation de la requête précédente
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
try {
setIsLoading(true);
setError(null);
const searchResults = await searchFunction(searchQuery, {
signal: abortControllerRef.current.signal
});
// Mise en cache du résultat
cleanCache();
cache.current.set(searchQuery, {
data: searchResults,
timestamp: Date.now()
});
setResults(searchResults);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
setResults([]);
}
} finally {
setIsLoading(false);
}
};
useEffect(() => {
// Nettoyage du timer précédent
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
if (query.length < minQueryLength) {
setResults([]);
setIsLoading(false);
return;
}
// Debouncing de la recherche
debounceTimerRef.current = setTimeout(() => {
performSearch(query);
}, debounceMs);
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
};
}, [query, debounceMs, minQueryLength]);
// Nettoyage lors du démontage
useEffect(() => {
return () => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
};
}, []);
return {
query,
setQuery,
results,
isLoading,
error,
cacheStats: {
size: cache.current.size,
hitRate: (cache.current.size / (cache.current.size + 1)) * 100
}
};
};
// Composant de recherche utilisant le hook optimisé
const SmartSearchBox = ({ onSearch }) => {
const searchAPI = async (query, { signal }) => {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal
});
if (!response.ok) {
throw new Error('Erreur de recherche');
}
return response.json();
};
const {
query,
setQuery,
results,
isLoading,
error,
cacheStats
} = useOptimizedSearch(searchAPI, {
debounceMs: 250,
cacheSize: 50,
minQueryLength: 2
});
return (
<div className="search-container">
<div className="search-input-wrapper">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Rechercher des produits..."
className="search-input"
/>
{isLoading && <div className="loading-spinner"></div>}
</div>
<div className="search-stats">
<span>Cache: {cacheStats.size} entrées</span>
<span>Taux de réussite: {cacheStats.hitRate.toFixed(1)}%</span>
</div>
{error && (
<div className="error-message">
Erreur: {error}
</div>
)}
<div className="search-results">
{results.map((item, index) => (
<div key={item.id} className="search-result-item">
<span>{item.name}</span>
<span>{item.price}€</span>
</div>
))}
</div>
</div>
);
};« La virtualisation permet de gérer des listes de 1 million d’éléments avec la même fluidité que 100 éléments »
— React Performance Benchmarks 2026