← Назад к вопросам

Как работают хуки, способствующие мемоизации?

2.3 Middle🔥 161 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Мемоизация в React: useMemo и useCallback

Мемоизация — это техника кэширования результатов функции, чтобы избежать повторных расчётов при одинаковых входных параметрах. В React есть два специальных хука для мемоизации: useMemo и useCallback. Они помогают оптимизировать производительность путём предотвращения ненужных пересчётов и ре-рендеров.

useMemo: кэширование значений

useMemo мемоизирует вычисленное значение и пересчитывает его только когда зависимости изменились.

Синтаксис

const memoizedValue = useMemo(() => {
  // Дорогостоящее вычисление
  return computeExpensiveValue(a, b);
}, [a, b]);  // Зависимости

useMemo принимает две аргумента:

  1. Функция, которая возвращает значение для мемоизации
  2. Массив зависимостей — пересчет происходит только если они изменились

Практический пример

import { useMemo } from 'react';

// Функция для фильтрации и сортировки большого списка
const filterAndSortUsers = (users, searchTerm) => {
  console.log('Фильтруем и сортируем...');
  return users
    .filter(user => user.name.includes(searchTerm))
    .sort((a, b) => a.name.localeCompare(b.name));
};

export function UserList({ users, searchTerm }) {
  // Плохо: фильтрация происходит при каждом ре-рендере
  const filteredUsers = filterAndSortUsers(users, searchTerm);

  return (
    <ul>
      {filteredUsers.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// Хорошо: фильтрация кэшируется
export function OptimizedUserList({ users, searchTerm }) {
  const filteredUsers = useMemo(() => {
    console.log('Фильтруем (только если нужно)');
    return filterAndSortUsers(users, searchTerm);
  }, [users, searchTerm]);  // Пересчитываем только если users или searchTerm изменились

  return (
    <ul>
      {filteredUsers.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Сложный пример

export function DataAnalysis({ data }) {
  // Дорогостоящий расчёт
  const statistics = useMemo(() => {
    console.time('Calculating statistics');
    
    const sum = data.reduce((acc, val) => acc + val, 0);
    const avg = sum / data.length;
    const max = Math.max(...data);
    const min = Math.min(...data);
    const median = data.sort((a, b) => a - b)[Math.floor(data.length / 2)];
    
    console.timeEnd('Calculating statistics');
    
    return { sum, avg, max, min, median };
  }, [data]);  // Пересчитываем только если data изменился

  return (
    <div>
      <p>Сумма: {statistics.sum}</p>
      <p>Средняя: {statistics.avg}</p>
      <p>Макс: {statistics.max}</p>
      <p>Мин: {statistics.min}</p>
      <p>Медиана: {statistics.median}</p>
    </div>
  );
}

useCallback: кэширование функций

useCallback мемоизирует функцию, чтобы при ре-рендере передавать ту же ссылку на функцию дочерним компонентам.

Синтаксис

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);  // Зависимости

Проблема без useCallback

import { useState } from 'react';

// Дочерний компонент
const Button = ({ onClick }) => {
  console.log('Button re-rendered');
  return <button onClick={onClick}>Click me</button>;
};

// Родительский компонент
export function Parent() {
  const [count, setCount] = useState(0);

  // Плохо: новая функция создаётся при каждом ре-рендере
  const handleClick = () => {
    setCount(count + 1);
  };

  console.log('Parent re-rendered');

  return (
    <div>
      <p>Count: {count}</p>
      {/* Button ре-рендерится каждый раз, потому что handleClick — новая функция */}
      <Button onClick={handleClick} />
    </div>
  );
}

Решение с useCallback

import { useState, useCallback } from 'react';

// Оптимизированный дочерний компонент с React.memo
const Button = React.memo(({ onClick }) => {
  console.log('Button re-rendered');
  return <button onClick={onClick}>Click me</button>;
});

export function OptimizedParent() {
  const [count, setCount] = useState(0);

  // Хорошо: функция кэшируется и не создаётся заново каждый раз
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);  // Зависимость от count

  console.log('Parent re-rendered');

  return (
    <div>
      <p>Count: {count}</p>
      {/* Button НЕ ре-рендерится, если handleClick не изменилась */}
      <Button onClick={handleClick} />
    </div>
  );
}

Практический пример с формой

import { useCallback } from 'react';

const SearchBox = React.memo(({ onSearch }) => {
  const [value, setValue] = useState('');

  const handleChange = (e) => {
    setValue(e.target.value);
    onSearch(e.target.value);  // Вызываем callback
  };

  return <input onChange={handleChange} placeholder="Поиск..." />;
});

export function SearchResults({ items }) {
  const [searchTerm, setSearchTerm] = useState('');

  // Мемоизируем callback, чтобы SearchBox не ре-рендерился
  const handleSearch = useCallback((term) => {
    setSearchTerm(term);
  }, []);

  const filtered = items.filter(item =>
    item.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <>
      <SearchBox onSearch={handleSearch} />
      <ul>
        {filtered.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  );
}

Сравнение useMemo и useCallback

// useMemo — кэширует вычисленное значение
const value = useMemo(() => {
  return expensiveCalculation(a, b);
}, [a, b]);

// useCallback — кэширует функцию (эквивалентно useMemo)
const callback = useCallback(() => {
  return expensiveCalculation(a, b);
}, [a, b]);

// Это то же самое:
const callback = useMemo(() => {
  return () => expensiveCalculation(a, b);
}, [a, b]);

Когда использовать мемоизацию

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

  • Вычисление дорогостоящее (фильтрация больших списков, матем расчёты)
  • Результат передаётся в дочерние компоненты (memo компоненты)
  • Вычисление вызывает побочные эффекты в других местах
const expensiveValue = useMemo(() => {
  // Вычисление занимает много времени
  return heavyCalculation(items);
}, [items]);

Используй useCallback когда:

  • Callback передаётся в оптимизированный дочерний компонент (React.memo)
  • Callback — зависимость другого хука (useEffect, другие хуки)
  • Функция используется как ключ в карте
const handleClick = useCallback(() => {
  doSomething();
}, [dependency]);

// Использование с React.memo
const Child = React.memo(({ onClick }) => {
  return <button onClick={onClick}>Click</button>;
});

return <Child onClick={handleClick} />;

Частые ошибки

Ошибка 1: неправильные зависимости

// Плохо: зависимость пропущена
const value = useMemo(() => {
  return calculate(a, b);  // Использует b
}, [a]);  // b пропущена!

// Хорошо
const value = useMemo(() => {
  return calculate(a, b);
}, [a, b]);

Ошибка 2: чрезмерная мемоизация

// Плохо: мемоизация простого вычисления замедляет код
const double = useMemo(() => count * 2, [count]);

// Хорошо: просто вычисли
const double = count * 2;

Мемоизация — мощный инструмент для оптимизации, но используй её мудро. Основное правило: сначала убедись что есть проблема производительности, потом добавляй мемоизацию.

Как работают хуки, способствующие мемоизации? | PrepBro