Introduction
Les performances sont cruciales pour l'expérience utilisateur. Ce guide explore les techniques d'optimisation React les plus efficaces.
1. React.memo pour éviter les re-renders
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('Render ExpensiveComponent');
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
});
// Comparaison personnalisée
const SmartComponent = React.memo(({ user, settings }) => {
return <div>{user.name} - {settings.theme}</div>;
}, (prevProps, nextProps) => {
return prevProps.user.id === nextProps.user.id &&
prevProps.settings.theme === nextProps.settings.theme;
});
2. useMemo pour les calculs coûteux
function ExpensiveCalculation({ items, filter }) {
// ❌ Mauvais : recalculé à chaque render
const expensiveValue = items
.filter(item => item.category === filter)
.reduce((sum, item) => sum + item.value, 0);
// ✅ Bon : mémorisé
const memoizedValue = useMemo(() => {
console.log('Calcul coûteux exécuté');
return items
.filter(item => item.category === filter)
.reduce((sum, item) => sum + item.value, 0);
}, [items, filter]);
return <div>Total: {memoizedValue}</div>;
}
3. useCallback pour les fonctions
function Parent({ items }) {
const [count, setCount] = useState(0);
// ❌ Nouvelle fonction à chaque render
const handleClick = (id) => {
console.log('Clicked:', id);
};
// ✅ Fonction mémorisée
const memoizedHandleClick = useCallback((id) => {
console.log('Clicked:', id);
// Si on utilise state, l'ajouter aux dépendances
}, []); // Dépendances vides car pas de state utilisé
const handleClickWithState = useCallback((id) => {
console.log('Clicked:', id, 'Count:', count);
}, [count]); // count en dépendance
return (
<div>
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
{items.map(item => (
<Child
key={item.id}
item={item}
onClick={memoizedHandleClick}
/>
))}
</div>
);
}
const Child = React.memo(({ item, onClick }) => {
console.log('Render Child:', item.id);
return (
<div onClick={() => onClick(item.id)}>
{item.name}
</div>
);
});
4. Lazy loading des composants
import { lazy, Suspense } from 'react';
// Lazy loading
const LazyComponent = lazy(() => import('./LazyComponent'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
const [showAdmin, setShowAdmin] = useState(false);
return (
<div>
<h1>Mon App</h1>
<Suspense fallback={<div>Chargement...</div>}>
<LazyComponent />
</Suspense>
{showAdmin && (
<Suspense fallback={<div>Chargement admin...</div>}>
<AdminPanel />
</Suspense>
)}
</div>
);
}
5. Virtualisation pour les grandes listes
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
>
{Row}
</FixedSizeList>
);
}
6. Optimisation des images
import Image from 'next/image';
// Next.js Image avec optimisation automatique
function OptimizedImage() {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={800}
height={600}
priority // Pour les images above-the-fold
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
);
}
// Lazy loading manuel
function LazyImage({ src, alt }) {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsLoaded(true);
observer.disconnect();
}
}
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={imgRef}>
{isLoaded ? (
<img src={src} alt={alt} />
) : (
<div className="placeholder">Loading...</div>
)}
</div>
);
}
7. Debouncing pour les inputs
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// Debounce avec useCallback et useEffect
const debouncedSearch = useCallback(
debounce(async (searchQuery) => {
if (searchQuery) {
const data = await searchAPI(searchQuery);
setResults(data);
}
}, 300),
[]
);
useEffect(() => {
debouncedSearch(query);
}, [query, debouncedSearch]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Rechercher..."
/>
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}
8. Code splitting par route
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { lazy, Suspense } from 'react';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Chargement...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
9. Outils de debugging des performances
// React DevTools Profiler
import { Profiler } from 'react';
function onRenderCallback(id, phase, actualDuration) {
console.log('Profiler:', { id, phase, actualDuration });
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<Header />
<Main />
<Footer />
</Profiler>
);
}
// Mesure personnalisée
function ComponentWithMetrics() {
useEffect(() => {
const start = performance.now();
return () => {
const end = performance.now();
console.log(`Component mounted in ${end - start}ms`);
};
}, []);
return <div>Content</div>;
}
10. Bonnes pratiques générales
État local vs global
- Garder l'état au plus près des composants qui l'utilisent
- Éviter les props drilling excessifs
- Utiliser Context avec parcimonie
Éviter les objets/arrays inline
// ❌ Mauvais
<Component style={{ margin: 10 }} data={[1, 2, 3]} />
// ✅ Bon
const style = { margin: 10 };
const data = [1, 2, 3];
<Component style={style} data={data} />
Bundle analysis
# Analyser la taille du bundle
npm install -g webpack-bundle-analyzer
npm run build
npx webpack-bundle-analyzer build/static/js/*.js
Ces techniques permettent de créer des applications React rapides et fluides !
