# Chapitre 1 : useCallback et useMemo ## Le problème : re-rendus inutiles À chaque rendu d'un composant, tout son code est ré-exécuté : ```javascript 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 ( ); } ``` Si `products` contient 1000 éléments, le tri est fait à chaque rendu, même si rien n'a changé. ## useMemo : mémoriser une valeur `useMemo` mémorise le résultat d'un calcul et ne le recalcule que si les dépendances changent. ### Syntaxe ```javascript const memoizedValue = useMemo(() => computeValue(a, b), [a, b]); ``` ### Exemple : calcul coûteux ```javascript 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 ( ); } ``` ### Exemple : filtrage ```javascript function UserList({ users, searchTerm }) { const filteredUsers = useMemo(() => { return users.filter(user => user.name.toLowerCase().includes(searchTerm.toLowerCase()) ); }, [users, searchTerm]); return ( ); } ``` ## useCallback : mémoriser une fonction `useCallback` mémorise une fonction et ne la recrée que si les dépendances changent. ### Syntaxe ```javascript const memoizedFn = useCallback(() => { doSomething(a, b); }, [a, b]); ``` ### Pourquoi c'est utile ? En JavaScript, chaque fois qu'une fonction est définie, c'est une nouvelle référence : ```javascript 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. ### Exemple avec React.memo ```javascript // Composant enfant mémoïsé const ProductItem = React.memo(function ProductItem({ product, onSelect }) { console.log('ProductItem rendu:', product.name); return (
  • onSelect(product.id)}> {product.name}
  • ); }); // 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 ( ); } ``` ### Exemple avec dépendances ```javascript function SearchForm({ onSearch, category }) { const [query, setQuery] = useState(''); // La fonction dépend de category const handleSubmit = useCallback(() => { onSearch(query, category); }, [onSearch, query, category]); return (
    setQuery(e.target.value)} />
    ); } ``` ## React.memo : mémoriser un composant `React.memo` empêche un composant de se re-rendre si ses props n'ont pas changé. ```javascript const MyComponent = React.memo(function MyComponent({ name, onClick }) { console.log('Rendu de MyComponent'); return ; }); ``` **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. ## Quand utiliser useMemo ? **OUI** : - Calculs coûteux (tri, filtrage de grandes listes) - Création d'objets passés en props à des composants mémoïsés - Calculs dont le résultat est utilisé dans plusieurs endroits **NON** : - Calculs simples (addition, concaténation) - Composants qui se re-rendent de toute façon - Par défaut "au cas où" ## Quand utiliser useCallback ? **OUI** : - Fonctions passées à des composants enfants mémoïsés (React.memo) - Fonctions dans les dépendances de useEffect - Gestionnaires d'événements coûteux **NON** : - Fonctions utilisées uniquement localement - Composants enfants non mémoïsés - Par défaut "pour optimiser" ## Comparaison ^ 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 | ## Piège : dépendances incorrectes ```javascript // 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. ## Exercices ### Exercice 1 : Liste filtrée optimisée Créer une liste de produits avec un champ de recherche. Optimiser pour ne pas refiltrer si la recherche n'a pas changé. **Solution :** ```javascript 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 (
    setSearch(e.target.value)} placeholder="Rechercher..." />
    ); } ``` ### Exercice 2 : Callback stable Créer un composant parent avec plusieurs boutons enfants. Optimiser pour que les boutons ne se re-rendent pas inutilement. **Solution :** ```javascript const Button = React.memo(function Button({ id, onClick, children }) { console.log('Button rendu:', id); return ; }); function ButtonGroup() { const [clicked, setClicked] = useState(null); const handleClick = useCallback((id) => { setClicked(id); console.log('Bouton cliqué:', id); }, []); return (

    Dernier clic : {clicked}

    ); } ``` ## Points clés à retenir 1. **useMemo** mémorise une **valeur** calculée 2. **useCallback** mémorise une **fonction** 3. **React.memo** mémorise un **composant** 4. **Ne pas optimiser prématurément** - mesurer d'abord 5. **Dépendances complètes** - toujours lister toutes les valeurs utilisées [[:15_training:module4-react-avance:start|← Retour au module]] | [[:15_training:module4-react-avance:custom-hooks|Chapitre suivant : Custom Hooks →]]