Как реализовать мемоизацию в классовых компонентах?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Мемоизация в классовых компонентах 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
// - Только если есть проблема — оптимизируй
Итог
Мемоизация в классовых компонентах реализуется несколькими способами:
- shouldComponentUpdate — явное сравнение props/state
- PureComponent — автоматическое поверхностное сравнение
- useMemo (функциональные компоненты) — кэширование значений
- useCallback (функциональные компоненты) — кэширование функций
- Manual cache Map — кэш в свойствах класса
- memoize из lodash — обёртка функций с кэшем
Для современного React рекомендуется использовать функциональные компоненты с хуками, но классовые компоненты все ещё имеют свое место в legacy кодах.