Расскажите про хук - useMemo, как он работает, где применяется и для чего?

👨‍💻 Frontend Developer 🟡 Часто попадается 🎚️ Средний
#React #Hooks

Краткий ответ

useMemo — это хук в React, который позволяет мемоизировать (кэшировать) результат вычислений между рендерами. Он предотвращает повторные вычисления при каждом рендере, если зависимости не изменились.

Синтаксис: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

Ключевые особенности:

  • Оптимизирует производительность, кэшируя результаты тяжелых вычислений
  • Пересчитывает значение только при изменении зависимостей в массиве
  • Не заменяет useState или useEffect, а дополняет их
  • Может использоваться для стабилизации объектов и функций между рендерами

Пример использования:

function ExpensiveCalculation({ number }) {
  const expensiveValue = useMemo(() => {
    console.log('Вычисление...');
    return number * 2;
  }, [number]);
  
  return <div>Результат: {expensiveValue}</div>;
}

Полный ответ

Хук useMemo — один из встроенных хуков React, предназначенный для оптимизации производительности путем мемоизации результатов вычислений. Он помогает избежать повторных тяжелых вычислений при каждом рендере компонента. 🚀

Как работает useMemo?

useMemo принимает две функции:

  1. Функцию-вычислитель — которая возвращает результат
  2. Массив зависимостей — при изменении которых происходит пересчет
import React, { useMemo } from 'react';
 
function Component({ a, b }) {
  const memoizedValue = useMemo(() => {
    // Тяжелые вычисления
    return a * b;
  }, [a, b]); // Пересчитывается только если a или b изменились
  
  return <div>Результат: {memoizedValue}</div>;
}

Когда компонент рендерится:

  1. React проверяет массив зависимостей
  2. Если зависимости не изменились, возвращает кэшированное значение
  3. Если зависимости изменились, выполняет функцию и кэширует новый результат

Основные применения useMemo

1. Оптимизация тяжелых вычислений

function FibonacciCalculator({ number }) {
  const fibonacci = useMemo(() => {
    console.log('Вычисление числа Фибоначчи...');
    if (number <= 1) return number;
    
    let a = 0, b = 1;
    for (let i = 2; i <= number; i++) {
      [a, b] = [b, a + b];
    }
    return b;
  }, [number]);
  
  return <div>Число Фибоначчи: {fibonacci}</div>;
}

2. Стабилизация объектов

function UserProfile({ user }) {
  const userInfo = useMemo(() => ({
    name: user.firstName + ' ' + user.lastName,
    avatar: user.photoUrl,
    isAdmin: user.role === 'admin'
  }), [user]);
  
  // userInfo будет стабильным между рендерами
  return <UserCard user={userInfo} />;
}

3. Предотвращение лишних ререндеров дочерних компонентов

function Parent({ items }) {
  // Без useMemo объект создается заново при каждом рендере
  // const processedItems = items.map(item => ({ ...item, processed: true }));
  
  // С useMemo объект стабилен между рендерами
  const processedItems = useMemo(() => 
    items.map(item => ({ ...item, processed: true })),
    [items]
  );
  
  return <Child items={processedItems} />;
}
 
// В дочернем компоненте можно использовать React.memo
const Child = React.memo(function Child({ items }) {
  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
});

Когда лучше использовать useMemo

Используйте useMemo когда:

  1. Тяжелые вычисления — обработка больших массивов, сложные математические расчеты
  2. Создание объектов — чтобы избежать создания новых объектов при каждом рендере
  3. Передача в дочерние компоненты — для оптимизации с React.memo
  4. Работа с большими списками — фильтрация, сортировка, преобразование данных
function ProductList({ products, searchTerm, category }) {
  // Фильтрация и сортировка - дорогие операции
  const filteredProducts = useMemo(() => {
    return products
      .filter(product => 
        product.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
        product.category === category
      )
      .sort((a, b) => a.price - b.price);
  }, [products, searchTerm, category]);
  
  return (
    <ul>
      {filteredProducts.map(product => 
        <ProductItem key={product.id} product={product} />
      )}
    </ul>
  );
}

Когда лучше отказаться от useMemo

Избегайте useMemo когда:

  1. Простые вычисления — базовая арифметика, простые операции
  2. Часто меняющиеся зависимости — если зависимости меняются часто, кэш будет постоянно сбрасываться
  3. Небольшие массивы данных — для небольших наборов данных оптимизация не нужна
  4. Простые объекты — если объекты создаются быстро и не влияют на производительность
// ❌ Не стоит использовать useMemo для простых операций
function BadExample({ a, b }) {
  const sum = useMemo(() => a + b, [a, b]); // Избыточно!
  return <div>{sum}</div>;
}
 
// ✅ Просто используйте переменную
function GoodExample({ a, b }) {
  const sum = a + b; // Просто и эффективно
  return <div>{sum}</div>;
}

Распространенные ошибки

1. Использование useMemo без необходимости

// ❌ Избыточное использование
function Component({ name }) {
  const greeting = useMemo(() => `Привет, ${name}!`, [name]);
  return <h1>{greeting}</h1>;
}
 
// ✅ Просто используйте переменную
function Component({ name }) {
  const greeting = `Привет, ${name}!`;
  return <h1>{greeting}</h1>;
}

2. Неправильный массив зависимостей

// ❌ Ошибка: отсутствует зависимость
function Component({ user }) {
  const fullName = useMemo(() => {
    return user.firstName + ' ' + user.lastName;
  }, []); // user не в списке зависимостей!
  return <div>{fullName}</div>;
}
 
// ✅ Правильно: все зависимости указаны
function Component({ user }) {
  const fullName = useMemo(() => {
    return user.firstName + ' ' + user.lastName;
  }, [user]);
  return <div>{fullName}</div>;
}

3. Мутация объектов внутри useMemo

// ❌ Ошибка: мутируем объект
function Component({ items }) {
  const processedItems = useMemo(() => {
    items.forEach(item => item.processed = true); // Мутируем!
    return items;
  }, [items]);
  return <List items={processedItems} />;
}
 
// ✅ Правильно: создаем новый объект
function Component({ items }) {
  const processedItems = useMemo(() => {
    return items.map(item => ({ ...item, processed: true })); // Создаем новые
  }, [items]);
  return <List items={processedItems} />;
}

Резюме

useMemo — это хук React для:

  • Мемоизации результатов тяжелых вычислений
  • Оптимизации производительности компонентов
  • Стабилизации объектов и функций между рендерами
  • Предотвращения лишних ререндеров дочерних компонентов

Когда использовать:

  • Тяжелые вычисления (фильтрация, сортировка больших массивов)
  • Создание сложных объектов
  • Передача стабильных значений в дочерние компоненты
  • Работа с большими наборами данных

Когда избегать:

  • Простые вычисления (сложение, конкатенация строк)
  • Часто меняющиеся зависимости
  • Небольшие наборы данных
  • Когда производительность не критична

Лучшие практики:

  • Используйте только при реальной необходимости
  • Всегда указывайте правильный массив зависимостей
  • Не мутируйте объекты внутри useMemo
  • Не используйте для побочных эффектов (для этого есть useEffect)

useMemo — мощный инструмент оптимизации, но его не следует использовать бездумно. Сначала определите реальные проблемы с производительностью, а затем применяйте useMemo для их решения. 🚀


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪