React - useMemo : Optimisation des Calculs

Quand ton composant fait des calculs coûteux à chaque rendu, useMemo va mémoriser le résultat et ne le recalculer que si les dépendances changent.


Le Problème des Recalculs Inutiles

Calculs Coûteux à Chaque Rendu

function SlowComponent({ items, filter }) {
  const [count, setCount] = useState(0)

  // ❌ PROBLÈME - Calcul coûteux à CHAQUE rendu !
  const expensiveValue = items
    .filter(item => item.category === filter)
    .reduce((sum, item) => sum + item.price, 0)
  
  console.log('💰 Calcul coûteux exécuté !') // ← Se déclenche même quand count change !

  // ❌ PROBLÈME - Nouvel objet à chaque rendu !
  const chartConfig = {
    type: 'bar',
    data: expensiveValue,
    options: { responsive: true }
  }

  // ❌ PROBLÈME - Tableau recalculé à chaque rendu !
  const sortedItems = items
    .filter(item => item.category === filter)
    .sort((a, b) => b.price - a.price)

  return (
    <div>
      <h3>Total: {expensiveValue}€</h3>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      
      <ExpensiveChart config={chartConfig} />
      <ItemsList items={sortedItems} />
    </div>
  )
}

// Ces composants re-render même si leurs props n'ont pas vraiment changé !
const ExpensiveChart = memo(function ExpensiveChart({ config }) {
  console.log('📊 ExpensiveChart re-rendu') // ← Toujours appelé car config est toujours un nouvel objet !
  return <div>Chart avec config: {JSON.stringify(config)}</div>
})

const ItemsList = memo(function ItemsList({ items }) {
  console.log('📝 ItemsList re-rendu') // ← Toujours appelé car items est toujours un nouveau tableau !
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name} - {item.price}€</li>
      ))}
    </ul>
  )
})

useMemo à la Rescousse

Syntaxe et Usage Basique

import { useMemo } from 'react'

function OptimizedComponent({ items, filter }) {
  const [count, setCount] = useState(0)

  // ✅ SOLUTION - Calcul seulement si items ou filter changent
  const expensiveValue = useMemo(() => {
    console.log('💰 Calcul coûteux exécuté') // ← Seulement si nécessaire !
    
    return items
      .filter(item => item.category === filter)
      .reduce((sum, item) => sum + item.price, 0)
  }, [items, filter]) // ← Dépendances

  // ✅ Objet complexe mémorisé
  const chartConfig = useMemo(() => {
    console.log('📊 Configuration chart générée')
    
    return {
      type: 'bar',
      data: expensiveValue,
      options: { responsive: true },
      theme: 'modern'
    }
  }, [expensiveValue])

  // ✅ Données filtrées et triées mémorisées
  const sortedItems = useMemo(() => {
    console.log('🔍 Filtrage et tri des items')
    
    return items
      .filter(item => item.category === filter)
      .sort((a, b) => b.price - a.price) // Tri par prix décroissant
  }, [items, filter])

  return (
    <div>
      <h3>Total: {expensiveValue}€</h3>
      <p>Count: {count} (ne déclenche plus le calcul !)</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      
      <ExpensiveChart config={chartConfig} />
      <ItemsList items={sortedItems} />
    </div>
  )
}

// Maintenant ces composants ne re-render que si leurs props changent vraiment !
const ExpensiveChart = memo(function ExpensiveChart({ config }) {
  console.log('📊 ExpensiveChart re-rendu') // ← Seulement si config change vraiment
  return <div>Chart avec config: {JSON.stringify(config)}</div>
})

const ItemsList = memo(function ItemsList({ items }) {
  console.log('📝 ItemsList re-rendu') // ← Seulement si items change vraiment
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.name} - {item.price}€</li>
      ))}
    </ul>
  )
})

Ressources Pour Aller Plus Loin