À chaque rendu d'un composant, tout son code est ré-exécuté :
function ProductList({ products }) { // Recalculé à CHAQUE rendu, même si products n'a pas changé const sortedProducts = products.sort((a, b) => a.price - b.price); // Nouvelle fonction créée à CHAQUE rendu const handleClick = (id) => { console.log('Clicked:', id); }; return ( <ul> {sortedProducts.map(p => ( <ProductItem key={p.id} product={p} onClick={handleClick} /> ))} </ul> ); }
Si products contient 1000 éléments, le tri est fait à chaque rendu, même si rien n'a changé.
useMemo mémorise le résultat d'un calcul et ne le recalcule que si les dépendances changent.
const memoizedValue = useMemo(() => computeValue(a, b), [a, b]);
import { useMemo } from 'react'; function ProductList({ products, sortBy }) { // Recalculé SEULEMENT si products ou sortBy change const sortedProducts = useMemo(() => { console.log('Tri en cours...'); return [...products].sort((a, b) => { if (sortBy === 'price') return a.price - b.price; if (sortBy === 'name') return a.name.localeCompare(b.name); return 0; }); }, [products, sortBy]); return ( <ul> {sortedProducts.map(p => ( <li key={p.id}>{p.name} - {p.price}€</li> ))} </ul> ); }
function UserList({ users, searchTerm }) { const filteredUsers = useMemo(() => { return users.filter(user => user.name.toLowerCase().includes(searchTerm.toLowerCase()) ); }, [users, searchTerm]); return ( <ul> {filteredUsers.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
useCallback mémorise une fonction et ne la recrée que si les dépendances changent.
const memoizedFn = useCallback(() => { doSomething(a, b); }, [a, b]);
En JavaScript, chaque fois qu'une fonction est définie, c'est une nouvelle référence :
const fn1 = () => console.log('hello'); const fn2 = () => console.log('hello'); fn1 === fn2; // false ! Ce sont deux fonctions différentes
Cela pose problème avec React.memo et les dépendances de useEffect.
// Composant enfant mémoïsé const ProductItem = React.memo(function ProductItem({ product, onSelect }) { console.log('ProductItem rendu:', product.name); return ( <li onClick={() => onSelect(product.id)}> {product.name} </li> ); }); // Composant parent function ProductList({ products }) { const [selected, setSelected] = useState(null); // SANS useCallback : nouvelle fonction à chaque rendu // → ProductItem est re-rendu même si product n'a pas changé const handleSelect = (id) => { setSelected(id); }; // AVEC useCallback : même fonction tant que les dépendances ne changent pas const handleSelectMemo = useCallback((id) => { setSelected(id); }, []); // [] car setSelected est stable return ( <ul> {products.map(p => ( <ProductItem key={p.id} product={p} onSelect={handleSelectMemo} /> ))} </ul> ); }
function SearchForm({ onSearch, category }) { const [query, setQuery] = useState(''); // La fonction dépend de category const handleSubmit = useCallback(() => { onSearch(query, category); }, [onSearch, query, category]); return ( <form onSubmit={handleSubmit}> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <button type="submit">Rechercher</button> </form> ); }
React.memo empêche un composant de se re-rendre si ses props n'ont pas changé.
const MyComponent = React.memo(function MyComponent({ name, onClick }) { console.log('Rendu de MyComponent'); return <button onClick={onClick}>{name}</button>; });
Important : React.memo compare les props par référence. Si une fonction est recréée à chaque rendu, le composant sera quand même re-rendu.
OUI :
NON :
OUI :
NON :
| Hook | Mémorise | Recalcule si |
|---|---|---|
| useMemo | Une valeur | Les dépendances changent |
| useCallback | Une fonction | Les dépendances changent |
| React.memo | Un composant | Les props changent |
// PROBLÈME : items manque dans les dépendances const getTotal = useCallback(() => { return items.reduce((sum, item) => sum + item.price, 0); }, []); // items devrait être dans le tableau ! // CORRECT const getTotal = useCallback(() => { return items.reduce((sum, item) => sum + item.price, 0); }, [items]);
ESLint avec le plugin eslint-plugin-react-hooks vous avertira des dépendances manquantes.
Créer une liste de produits avec un champ de recherche. Optimiser pour ne pas refiltrer si la recherche n'a pas changé.
Solution :
function OptimizedProductList({ products }) { const [search, setSearch] = useState(''); const [sortBy, setSortBy] = useState('name'); const filteredAndSorted = useMemo(() => { console.log('Filtrage et tri...'); return products .filter(p => p.name.toLowerCase().includes(search.toLowerCase())) .sort((a, b) => { if (sortBy === 'name') return a.name.localeCompare(b.name); if (sortBy === 'price') return a.price - b.price; return 0; }); }, [products, search, sortBy]); return ( <div> <input value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Rechercher..." /> <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}> <option value="name">Par nom</option> <option value="price">Par prix</option> </select> <ul> {filteredAndSorted.map(p => ( <li key={p.id}>{p.name} - {p.price}€</li> ))} </ul> </div> ); }
Créer un composant parent avec plusieurs boutons enfants. Optimiser pour que les boutons ne se re-rendent pas inutilement.
Solution :
const Button = React.memo(function Button({ id, onClick, children }) { console.log('Button rendu:', id); return <button onClick={() => onClick(id)}>{children}</button>; }); function ButtonGroup() { const [clicked, setClicked] = useState(null); const handleClick = useCallback((id) => { setClicked(id); console.log('Bouton cliqué:', id); }, []); return ( <div> <p>Dernier clic : {clicked}</p> <Button id="a" onClick={handleClick}>Bouton A</Button> <Button id="b" onClick={handleClick}>Bouton B</Button> <Button id="c" onClick={handleClick}>Bouton C</Button> </div> ); }