Optimisation React

Re-renders, React.memo, useMemo, useCallback & Code Splitting

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. Comprendre les 3 raisons de re-render

State, parent, context

2. Utiliser React.memo

Éviter les re-renders inutiles

3. Maîtriser useMemo

Mémoriser les calculs coûteux

4. Stabiliser avec useCallback

Callbacks stables en props

5. Implémenter le code splitting

React.lazy + Suspense pour des bundles plus légers

Plan du cours

1

Pourquoi un composant re-rend ?

Les 3 raisons : state, parent, context

2

React.memo

Empêcher les re-renders quand les props n'ont pas changé

3

useMemo et useCallback

Stabiliser les valeurs et fonctions passées en props

4

React.lazy + Suspense

Charger les composants Ă  la demande (code splitting)

5

Live coding

Profiler une app et optimiser les re-renders inutiles

Module 1

Pourquoi un composant re-rend ?

Les 3 raisons qui déclenchent un re-render

Qu'est-ce qu'un re-render ?

React exécute à nouveau la fonction du composant

Rendu initial

Le composant s'affiche pour la première fois

ReactDOM.createRoot()

Re-render

React ré-exécute le composant

Component()

// → nouveau JSX

💡 Re-render ≠ Re-paint du DOM. React compare d'abord le nouveau JSX avec le DOM existant

Raison 1 : Le state change

Quand setState est appelé, le composant re-rend

function Counter() {

const [count, setCount] = useState(0);

return (

<div>

<p>Count: {count}</p>

<button onClick={() => setCount(count + 1)}>

+1

</button>

</div>

);

}

âś… C'est le comportement normal : on VEUT que le composant se mette Ă  jour!

Raison 2 : Le parent re-rend

Si le parent re-rend, tous les enfants re-rendent automatiquement

Parent

count change

→

Child A

re-render!

→

Child B

re-render!

⚠️ Même si Child A et Child B n'utilisent pas count, ils re-rendent quand même!

Raison 3 : Le context change

Tous les composants qui consomment le context re-rendent

const ThemeContext = createContext('light');

function App() {

const [theme, setTheme] = useState('light');

return (

<ThemeContext.Provider value={theme}>

<Header /> // Re-render

<Sidebar /> // Re-render

<Main /> // Re-render

</ThemeContext.Provider>

);

}

đź’ˇ Tous les consommateurs du context re-rendent, mĂŞme s'ils n'utilisent que d'autres valeurs

Les 3 raisons de re-render

1

State change

setState() appelé

2

Parent re-render

Cascade automatique

3

Context change

Provider value change

⚠️ Les re-renders ne sont pas toujours un problème!

Mais quand ils deviennent coûteux, il faut optimiser

Module 2

React.memo

Empêcher les re-renders quand les props n'ont pas changé

React.memo : Le concept

Un HOC (Higher-Order Component) qui "mémoïse" un composant

Sans React.memo

Le composant re-rend Ă  chaque fois que le parent re-rend

Performance: ⚠️

Avec React.memo

Le composant ne re-rend que si les props changent

Performance: âś…

const MemoizedChild = React.memo(Child);

Syntaxe de React.memo

function Child({ name }: { name: string }) {

return <div>Hello {name}</div>;

}

export default React.memo(Child);

React compare les props avec Object.is()

Si les props sont identiques → pas de re-render

⚠️ Attention : comparaison superficielle (shallow comparison)

Les objets et tableaux sont comparés par référence, pas par contenu!

Quand React.memo fonctionne

âś… Props stables : primitives (string, number, boolean)

function Parent() {

const [count, setCount] = useState(0);

const name = "Alice"; // Primitif stable

return (

<>

<button onClick={() => setCount(count + 1)}>{count}</button>

<Child name={name} /> // âś… Ne re-rend pas!

</>

);

}

🎉 name est une string primitive → comparaison par valeur → stable

Quand React.memo échoue

❌ Props instables : objets, tableaux, fonctions créés dans le render

function Parent() {

const [count, setCount] = useState(0);

const config = { color: 'blue' }; // ❌ Nouvel objet à chaque render!

return (

<>

<button onClick={() => setCount(count + 1)}>{count}</button>

<Child config={config} /> // ❌ Re-rend à chaque fois!

</>

);

}

⚠️ { color: 'blue' } est un nouvel objet à chaque render → référence différente → React.memo ne peut pas optimiser

La solution : stabiliser les props

❌ Objet inline

<Child config={{ color: 'blue' }} />

Nouvel objet Ă  chaque render

âś… Objet stable avec useMemo

const config = useMemo(

() => ({ color: 'blue' }),

[]

);

Même référence entre les renders

đź’ˇ C'est lĂ  que useMemo et useCallback deviennent essentiels!

Module 3

useMemo et useCallback

Stabiliser les valeurs et fonctions passées en props

useMemo : Mémoriser un calcul

Retourne une valeur mémorisée qui ne change que si les dépendances changent

const memoizedValue = useMemo(

() => computeExpensiveValue(a, b),

[a, b] // Dépendances

);

() => valeur

Fonction de calcul

[deps]

Tableau de dépendances

useMemo : Exemple pratique

Calcul coûteux : filtrer une liste de 10 000 éléments

function ProductList({ products, filter }: Props) {

const visibleProducts = useMemo(() => {

return products.filter(p => p.category === filter);

}, [products, filter]); // Recalcule seulement si products ou filter change

return <ul>{visibleProducts.map(...)}</ul>;

}

✅ Le filtrage ne s'exécute que quand products ou filter change

useMemo : Objet stable pour React.memo

Créer un objet qui garde la même référence

function Parent() {

const style = useMemo(() => ({

color: 'blue',

fontSize: 16

}), []); // Dépendances vides = jamais recréé

return <Child style={style} />; // âś… Props stable!

}

🎉 style garde la même référence → React.memo fonctionne!

useCallback : Stabiliser une fonction

Retourne une fonction mémorisée qui ne change que si les dépendances changent

const memoizedCallback = useCallback(

() => { doSomething(a, b); },

[a, b] // Dépendances

);

đź’ˇ useCallback est un raccourci pour useMemo(() => fn, [deps])

useCallback : Exemple pratique

Passer un callback stable Ă  un composant React.memo

function Parent() {

const [count, setCount] = useState(0);

const handleClick = useCallback(() => {

console.log('Clicked!');

}, []); // Dépendances vides = fonction stable

return <Child onClick={handleClick} />; // âś… Props stable!

}

🎉 handleClick garde la même référence → Child (React.memo) ne re-rend pas!

useMemo vs useCallback

useMemo

Mémorise une valeur

✅ Calculs coûteux

âś… Objets/tableaux stables

✅ Résultats de transformations

const value = useMemo(

() => compute(), [deps]

);

useCallback

Mémorise une fonction

✅ Callbacks passés en props

âś… Handlers pour composants memo

✅ Dépendances de useEffect

const fn = useCallback(

() => {}, [deps]

);

Module 4

React.lazy + Suspense

Charger les composants Ă  la demande (code splitting)

Code Splitting : Le concept

Diviser le bundle JavaScript en plusieurs fichiers chargés séparément

Sans code splitting

Un seul gros bundle.js

bundle.js

2.5 MB

⚠️ Chargement initial lent

Avec code splitting

Plusieurs petits bundles

main.js

500 KB

admin.js

300 KB

âś… Chargement initial rapide

React.lazy : Syntaxe

Charger un composant dynamiquement avec import()

// Avant : import statique

import Dashboard from './Dashboard';

// Après : import dynamique avec React.lazy

const Dashboard = React.lazy(() => import('./Dashboard'));

⚠️ React.lazy doit être utilisé avec Suspense

Suspense : Afficher un fallback

Afficher un loader pendant le chargement du composant

const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {

return (

<Suspense fallback={<Loading />}>

<Dashboard />

</Suspense>

);

}

âś… Le composant Loading s'affiche pendant que Dashboard se charge

Route-based code splitting

Pattern recommandé : charger chaque route séparément

const Home = lazy(() => import('./pages/Home'));

const About = lazy(() => import('./pages/About'));

const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {

return (

<Suspense fallback={<PageLoader />}>

<Routes>

<Route path="/" element={<Home />} />

<Route path="/about" element={<About />} />

<Route path="/dashboard" element={<Dashboard />} />

</Routes>

</Suspense>

);

}

Module 5

Live Coding & Profiling

Identifier et optimiser les re-renders inutiles

React DevTools Profiler

L'outil indispensable pour mesurer les performances

Installer

Extension Chrome/Firefox : React Developer Tools

Utiliser

Onglet "Profiler" → Record → Interagir → Analyser

đź’ˇ Mesurer avant d'optimiser! Ne pas optimiser "au cas oĂą"

Workflow d'optimisation

1

Profil avec React DevTools

Identifier les composants qui re-rendent souvent

2

Vérifier si le re-render est nécessaire

Les props ont-elles changé ? Le state a-t-il changé ?

3

Appliquer l'optimisation appropriée

React.memo, useMemo, useCallback selon le cas

4

Re-profiler pour vérifier l'amélioration

Comparer les temps de render avant/après

Pièges courants

Les erreurs à éviter

Ce qu'il ne faut PAS faire

Piège 1 : Optimisation prématurée

❌ Mauvais

Mettre React.memo partout "au cas oĂą"

// Tout est memoized...

export default React.memo(Header);

export default React.memo(Footer);

export default React.memo(Button);

⚠️ Complexité inutile + overhead de comparaison

âś… Bon

Mesurer d'abord, optimiser ensuite

// 1. Profiler

// 2. Identifier le bottleneck

// 3. Optimiser ce composant

✅ Optimisation ciblée et justifiée

Piège 2 : Objet inline en props

Passer un objet inline Ă  un composant React.memo

❌ L'erreur

const Child = memo(({ config }) => ...);

function Parent() {

return (

<Child config={{ color: 'blue' }} />

);

}

{ } = nouvel objet Ă  chaque render!

âś… La solution

const config = useMemo(() => ({

color: 'blue'

}), []);

return <Child config={config} />;

Même référence = React.memo fonctionne!

Piège 3 : Stale closure avec useCallback

useCallback avec dépendances vides qui capture un state obsolète

❌ L'erreur

const [count, setCount] = useState(0);

const logCount = useCallback(() => {

console.log(count); // Toujours 0!

}, []); // Dépendances vides!

count est "capturé" à la valeur initiale

âś… La solution

const [count, setCount] = useState(0);

const logCount = useCallback(() => {

console.log(count);

}, [count]); // count dans les deps!

Fonction mise Ă  jour quand count change

Piège 4 : Confusion useMemo/useCallback

Ne pas confondre : useMemo = valeur, useCallback = fonction

// useCallback est un raccourci pour useMemo

const handleClick = useCallback(() => {

console.log('clicked');

}, []);

// Équivalent à :

const handleClick = useMemo(() => {

return () => {

console.log('clicked');

};

}, []);

đź’ˇ Retenez : useCallback(fn, deps) = useMemo(() => fn, deps)

Points clés à retenir

1. Les 3 raisons de re-render

State change, parent re-render, context change

2. React.memo ne fonctionne qu'avec des props stables

D'oĂą l'importance de useMemo et useCallback

3. useMemo pour les calculs, useCallback pour les callbacks

Les deux stabilisent les valeurs passées en props

4. React.lazy pour le code splitting par route

Chaque page se charge séparément

⚠️ Mesurer avant d'optimiser!

React DevTools Profiler est votre ami

Exercices pratiques

1. Identifier les re-renders

Utiliser React DevTools Profiler sur une app existante

2. Optimiser avec React.memo

Wrapper un composant et vérifier qu'il ne re-rend pas

3. Stabiliser les props

Utiliser useMemo pour un objet, useCallback pour une fonction

4. Implémenter le code splitting

Convertir les imports statiques en React.lazy avec Suspense

Questions?

L'optimisation est un art : mesurer, optimiser, vérifier

À retenir : Ne pas optimiser prématurément!

Utilisez React DevTools Profiler pour identifier les vrais problèmes