React15 de lecture5 octobre 2024

Guide ultime : optimiser les performances React

Techniques avancées pour optimiser vos applications React : memo, useMemo, useCallback, virtualization et plus encore. Avec des benchmarks réels.

Guide ultime : optimiser les performances React

Introduction

La performance est cruciale pour l'expérience utilisateur. Une application lente perd des utilisateurs. Voici les techniques pour garder votre app React rapide comme l'éclair.

Comprendre les re-renders

Pourquoi un composant re-render ?

  1. Son state change
  2. Ses props changent
  3. Son parent re-render
  4. Le context qu'il consomme change

Identifier les re-renders

// En développement
import { Profiler } from 'react'

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log({ id, phase, actualDuration })
}

<Profiler id="MyComponent" onRender={onRenderCallback}>
  <MyComponent />
</Profiler>

Mémoisation : les bases

React.memo

Évite les re-renders si les props n'ont pas changé :

// ❌ Re-render à chaque fois que le parent re-render
function ExpensiveList({ items }) {
  return items.map(item => <ExpensiveItem key={item.id} {...item} />)
}

// ✅ Re-render seulement si items change
const ExpensiveList = memo(function ExpensiveList({ items }) {
  return items.map(item => <ExpensiveItem key={item.id} {...item} />)
})

useMemo

Mémorise le résultat d'un calcul coûteux :

function Dashboard({ transactions }) {
  // ❌ Recalculé à chaque render
  const stats = calculateStats(transactions)

  // ✅ Recalculé seulement si transactions change
  const stats = useMemo(
    () => calculateStats(transactions),
    [transactions]
  )

  return <StatsDisplay stats={stats} />
}

useCallback

Mémorise une fonction :

function Parent() {
  const [count, setCount] = useState(0)

  // ❌ Nouvelle fonction à chaque render
  const handleClick = () => {
    console.log('clicked')
  }

  // ✅ Même référence entre les renders
  const handleClick = useCallback(() => {
    console.log('clicked')
  }, [])

  return <MemoizedChild onClick={handleClick} />
}

Optimisations avancées

1. Virtualization pour les longues listes

import { useVirtual } from '@tanstack/react-virtual'

function VirtualList({ items }) {
  const parentRef = useRef(null)

  const virtualizer = useVirtual({
    size: items.length,
    parentRef,
    estimateSize: useCallback(() => 50, []),
  })

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: virtualizer.totalSize }}>
        {virtualizer.virtualItems.map((virtualRow) => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: virtualRow.start,
              height: virtualRow.size,
            }}
          >
            {items[virtualRow.index].name}
          </div>
        ))}
      </div>
    </div>
  )
}

2. Code splitting avec lazy

import { lazy, Suspense } from 'react'

// Chargé à la demande
const HeavyChart = lazy(() => import('./HeavyChart'))
const AdminPanel = lazy(() => import('./AdminPanel'))

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/chart" element={<HeavyChart />} />
        <Route path="/admin" element={<AdminPanel />} />
      </Routes>
    </Suspense>
  )
}

3. Debounce pour les inputs

import { useDeferredValue, useState } from 'react'

function Search() {
  const [query, setQuery] = useState('')
  const deferredQuery = useDeferredValue(query)

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {/* Les résultats utilisent la valeur différée */}
      <SearchResults query={deferredQuery} />
    </>
  )
}

4. Optimiser le Context

// ❌ Tout re-render quand n'importe quelle valeur change
const AppContext = createContext({ user: null, theme: 'light', /* ... */ })

// ✅ Séparer les contexts par domaine
const UserContext = createContext(null)
const ThemeContext = createContext('light')

// ✅ Mémoiser la value du provider
function UserProvider({ children }) {
  const [user, setUser] = useState(null)

  const value = useMemo(
    () => ({ user, setUser }),
    [user]
  )

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  )
}

Mesurer les performances

Web Vitals

import { getCLS, getFID, getLCP } from 'web-vitals'

getCLS(console.log)  // Cumulative Layout Shift
getFID(console.log)  // First Input Delay
getLCP(console.log)  // Largest Contentful Paint

Chrome DevTools

  1. Performance tab : Enregistrez et analysez le timeline
  2. React DevTools Profiler : Identifiez les composants lents
  3. Lighthouse : Audit complet des performances

Checklist de performance

  • Utiliser React.memo pour les composants purs
  • Virtualiser les listes de plus de 100 éléments
  • Lazy load les routes et composants lourds
  • Optimiser les images (next/image, srcset)
  • Éviter les inline objects/functions dans JSX
  • Séparer les contexts par domaine
  • Utiliser useDeferredValue pour les inputs
  • Profiler régulièrement en production

Conclusion

L'optimisation des performances React est un processus continu. Mesurez d'abord, optimisez ensuite. N'optimisez pas prématurément, mais gardez ces techniques en tête lors du développement.

#React#Performance#Optimization
Partager cet article