Каждый второй React-разработчик сталкивался с ситуацией, когда интерфейс тормозит без видимых причин. Через десять минут расследование показывает: компонент перерендеривается двадцать раз вместо одного. Стандартный ответ — обернуть всё в useMemo
и React.memo
. Но слепая мемоизация часто усугубляет проблемы вместо их решения.
Корень проблемы: Когда рендеры выходят из-под контроля
Рассмотрим компонент списка задач с фильтрацией:
const TodoList = ({ todos, filter }) => {
const filtered = todos.filter(todo => todo.status === filter);
return (
<ul>
{filtered.map(todo => (
<TodoItem key={todo.id} {...todo} />
))}
</ul>
);
};
При каждом рендере родителя TodoList
будет:
- Создавать новый массив
filtered
- Пересоздавать все дочерние компоненты
TodoItem
Даже если todos
и filter
не изменились, сравнение пропсов в TodoItem
провалится — объекты todo каждый раз новые. Это классический пример ненужных ререндеров.
Решение 1: Мемоизация вычислений
const filtered = useMemo(
() => todos.filter(todo => todo.status === filter),
[todos, filter]
);
Кешируем результат фильтрации, сохраняя ссылку на массив между рендерами при неизменных зависимостях. Но этого недостаточно — сами TodoItem
всё равно будут пересоздаваться.
Решение 2: Стабилизация пропсов
const TodoItem = React.memo(({ id, title, status }) => {
// Реализация компонента
});
// В родительском компоненте:
{filtered.map(todo => (
<TodoItem
key={todo.id}
id={todo.id}
title={todo.title}
status={todo.status}
/>
))}
Теперь TodoItem
ререндерится только при изменении конкретных пропсов. Для объектов и функций используйте мемоизацию самих значений:
const todo = useMemo(
() => ({ id, title, status }),
[id, title, status]
);
Опасности преждевременной оптимизации
Мемоизация — не серебряная пуля. Каждый вызов useMemo
:
- Увеличивает потребление памяти
- Добавляет накладные расходы на сравнение зависимостей
- Усложняет код
Правило: Оптимизируйте только когда видите измеримые проблемы. Используйте React DevTools Profiler для точного определения узких мест.
Антипаттерны:
// Бесполезная мемоизация примитивов
const title = useMemo(() => props.title, [props.title]);
// Избыточная мемоизация компонентов
const MemoButton = React.memo(Button);
// ...
<MemoButton onClick={() => {/* Новый колбек каждый раз */}} />
Архитектурные решения до кода
- Разделяйте данные и представление
// Плохо:
<UserCard user={user} />
// Лучше:
<UserCard
name={user.name}
avatar={user.avatar}
// Вычисляемые значения в родителе:
isPremium={user.subscription?.status === 'active'}
/>
- Выносите статичные части за пределы компонентов
// Вместо:
const Footer = () => {
const links = [/*...*/]; // Новый массив при каждом рендере
return <Footer links={links} />;
}
// Переместить:
const LINKS = [/*...*/]; // Константа вне компонента
- Контролируйте обновления состояний
// Строки вместо объектов:
const [filters, setFilters] = useState('active');
// Вместо:
const [filters, setFilters] = useState({ status: 'active' });
Когда использовать мемоизацию обязательно
- Тяжёлые вычисления (фильтрация больших массивов, сложные математические операции)
- Передача компонентов как children
// Modal перерендерится при любом изменении в родителе
<Modal>
<ExpensiveComponent />
</Modal>
// Решение:
const content = useMemo(() => <ExpensiveComponent />, []);
<Modal>{content}</Modal>
- Контекст с часто изменяемыми значениями
const AuthContext = React.createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const value = useMemo(() => ({
user,
login: setUser
}), [user]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
};
Измеряйте результаты
Не доверяйте интуиции — используйте инструменты:
- React DevTools Profiler с опцией "Record why each component rendered"
- Бенчмаркинг через
window.performance.mark()
- Lighthouse аудиты с замедлением CPU
После оптимизации таблицы с 10k строк:
- Первоначальный рендер: 1200мс → 280мс
- Ввод текста в поле фильтрации: 450мс → 30мс
Главные принципы
- Мемоизация — лекарство с побочными эффектами, принимайте только по показаниям
- 80% проблем решаются правильным разделением компонентов
- Всегда проверяйте гипотезы инструментально
- Оптимизируйте в порядке влияния на UX: сначала частые взаимодействия, потом редкие
Последний совет: Избегайте глухой мемоизации всей кодовой базы. Создавайте точечные оптимизации там, где измерения подтверждают необходимость. React и современные браузеры умеют больше, чем кажется — иногда достаточно просто не мешать им работать.