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

Как реализовать мемоизацию в классовых компонентах?

2.3 Middle🔥 151 комментариев
#JavaScript Core

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

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

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

Мемоизация в классовых компонентах React

Мемоизация — это оптимизационная техника, которая кэширует результаты дорогостоящих операций. В React это очень важно для производительности.

Что такое мемоизация

// БЕЗ МЕМОИЗАЦИИ: функция вычисляется каждый раз
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

fibonacci(40); // вычисляется 2 секунды
fibonacci(40); // вычисляется ещё 2 секунды (одинаковый результат!)

// С МЕМОИЗАЦИЕЙ: результат кэшируется
function memoize(fn) {
  const cache = {};
  return function(n) {
    if (n in cache) {
      console.log('Возвращаю из кэша');
      return cache[n];
    }
    console.log('Вычисляю заново');
    const result = fn(n);
    cache[n] = result;
    return result;
  };
}

const fibMemo = memoize(fibonacci);
fibMemo(40); // вычисляет 2 секунды, кэширует результат
fibMemo(40); // мгновенно! (из кэша)

Способ 1: shouldComponentUpdate

// shouldComponentUpdate — самый базовый способ
class Calculator extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Если props не изменились — не перерендеривать
    if (this.props.a === nextProps.a && this.props.b === nextProps.b) {
      return false; // пропустить re-render
    }
    return true; // нужен re-render
  }

  render() {
    const { a, b } = this.props;
    return <div>{a + b}</div>;
  }
}

// ПРОБЛЕМА: нужно писать много сравнений
// ПРЕИМУЩЕСТВО: максимально явно и понятно

Способ 2: PureComponent

Автоматическое поверхностное сравнение props и state:

// React.PureComponent — делает shouldComponentUpdate автоматически
class Counter extends React.PureComponent {
  render() {
    const { count, onIncrement } = this.props;
    return <button onClick={onIncrement}>{count}</button>;
  }
}

// ЕСТЬ СРАВНЕНИЕ PROPS И STATE:
// Если они не изменились (поверхностно) — не рендеривает

// ПРОБЛЕМА: поверхностное сравнение может быть неправильным
const obj1 = { value: 1 };
const obj2 = { value: 1 };
obj1 === obj2; // false! (разные объекты)

// Это может привести к непредвиденным re-renders
class Parent extends React.Component {
  render() {
    const data = { a: 1 }; // создаётся НОВЫЙ объект каждый раз
    return <Counter data={data} />; // PureComponent видит новый объект и рендеривает
  }
}

Способ 3: useMemo в функциональных компонентах

Мемоизация вычисляемых значений. Хотя это функциональный компонент, это важно знать:

import { useMemo } from 'react';

function ListComponent({ items, filter }) {
  // Вычисляем только если items или filter изменились
  const filtered = useMemo(() => {
    console.log('Фильтрую элементы');
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]); // зависимости

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

// ПРИ КАЖДОМ RENDER:
// 1. Сравниваются зависимости [items, filter]
// 2. Если изменились — вычисляет новый filtered
// 3. Если не изменились — возвращает закэшированный результат

Способ 4: useCallback для функций

Кэширование функций, чтобы не создавать новые функции при каждом render:

import { useCallback } from 'react';

function Parent({ items }) {
  // БЕЗ useCallback: создаётся НОВАЯ функция каждый раз
  // const handleClick = () => console.log('Clicked');
  // PureComponent дочернего компонента видит новую функцию и рендеривает

  // С useCallback: функция кэшируется
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // зависимости пусты, функция кэшируется навсегда

  return <PureChild onClick={handleClick} />;
}

class PureChild extends React.PureComponent {
  render() {
    // Если handleClick не изменился (закэширован) — не рендеривает
    return <button onClick={this.props.onClick}>Click me</button>;
  }
}

Способ 5: Мемоизация дорогостоящих вычислений в методах

Это более сложный случай — когда в методе класса нужно кэшировать результат:

class DataProcessor extends React.Component {
  constructor(props) {
    super(props);
    // Кэш для хранения результатов
    this.cache = new Map();
  }

  // Мемоизированный метод
  processData = (data) => {
    const cacheKey = JSON.stringify(data); // или используй id

    // Если результат в кэше — возвращай его
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }

    // Вычисляем дорогостоящую операцию
    const result = data.map(item => ({
      ...item,
      processed: item.value * 2,
      timestamp: Date.now()
    }));

    // Кэшируем результат
    this.cache.set(cacheKey, result);

    // Ограничиваем размер кэша
    if (this.cache.size > 10) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }

    return result;
  };

  render() {
    const { data } = this.props;
    const processed = this.processData(data);
    return <div>{processed.length} items processed</div>;
  }
}

Способ 6: Мемоизация в селекторах (Redux паттерн)

Если используешь Redux или похожее состояние в классовом компоненте:

import { memoize } from 'lodash'; // или реализовать свой memoize

class UserList extends React.Component {
  // Мемоизированный селектор
  getVisibleUsers = memoize((users, filter) => {
    console.log('Фильтрую пользователей');
    return users.filter(u => u.name.includes(filter));
  });

  render() {
    const { users, filter } = this.props;

    // Если users и filter не изменились — возвращает кэшированный результат
    const visibleUsers = this.getVisibleUsers(users, filter);

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

Способ 7: React.memo для HOC обёртки

// Если у тебя классовый компонент, можно обернуть его в memo
const PureCounter = React.memo(
  class Counter extends React.Component {
    render() {
      const { count } = this.props;
      return <div>{count}</div>;
    }
  },
  // Опциональный comparer для сравнения props
  (prevProps, nextProps) => {
    // Если вернуть true — не рендеривать
    return prevProps.count === nextProps.count;
  }
);

Полный пример: оптимизированный классовый компонент

class ComplexList extends React.PureComponent {
  constructor(props) {
    super(props);
    this.cache = new Map();
    this.filterCache = new Map();
  }

  // Мемоизированная фильтрация
  filterItems = (items, query) => {
    const cacheKey = `${JSON.stringify(items)}_${query}`;

    if (this.filterCache.has(cacheKey)) {
      return this.filterCache.get(cacheKey);
    }

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

    this.filterCache.set(cacheKey, filtered);
    return filtered;
  };

  // Мемоизированная сортировка
  sortItems = (items, field) => {
    const cacheKey = `${JSON.stringify(items)}_${field}`;

    if (this.filterCache.has(cacheKey)) {
      return this.filterCache.get(cacheKey);
    }

    const sorted = [...items].sort((a, b) => a[field] > b[field] ? 1 : -1);

    this.filterCache.set(cacheKey, sorted);
    return sorted;
  };

  render() {
    const { items, query, sortBy } = this.props;

    let result = this.filterItems(items, query);
    result = this.sortItems(result, sortBy);

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

// ПОЧЕМУ PureComponent:
// - shouldComponentUpdate сравнивает props и state поверхностно
// - Если items и query не изменились (по ссылке) — не рендеривает
// - Экономит дорогостоящие filter и sort операции

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

// ИСПОЛЬЗУЙ:
// - Дорогостоящие вычисления (sort, filter больших массивов)
// - Часто рендеривается с одинаковыми props
// - Компонент имеет много дочерних компонентов

// НЕ ИСПОЛЬЗУЙ (оверинжиниринг):
// - Простые операции (основные вычисления)
// - Редко рендеривается
// - Нет заметных проблем с производительностью

// ИЗМЕРЯЙ:
// - Используй React DevTools Profiler
// - Смотри сколько времени на render
// - Только если есть проблема — оптимизируй

Итог

Мемоизация в классовых компонентах реализуется несколькими способами:

  1. shouldComponentUpdate — явное сравнение props/state
  2. PureComponent — автоматическое поверхностное сравнение
  3. useMemo (функциональные компоненты) — кэширование значений
  4. useCallback (функциональные компоненты) — кэширование функций
  5. Manual cache Map — кэш в свойствах класса
  6. memoize из lodash — обёртка функций с кэшем

Для современного React рекомендуется использовать функциональные компоненты с хуками, но классовые компоненты все ещё имеют свое место в legacy кодах.