useMemo и useCallback
При каждой отрисовке React заново выполняет весь код компонента: вычисления, создание функций. Обычно это быстро и незаметно. Хуки useMemo и useCallback позволяют запомнить (мемоизировать) значение или функцию и пересоздавать их, только когда изменились зависимости. Это оптимизация: без неё код работает так же, просто иногда медленнее.
useMemo: запомнить вычисление
Добавим в блог поиск по статьям. Фильтрация выполняется при каждой отрисовке — даже когда меняется не поисковая строка, а, например, тема оформления. В src/components/PostSearch.jsx:
// src/components/PostSearch.jsx
import { useMemo, useState } from 'react';
export default function PostSearch({ posts }) {
const [query, setQuery] = useState('');
const found = useMemo(
() => posts.filter((post) => post.title.includes(query.toLowerCase())),
[posts, query],
);
return (
<>
<input value={query} onChange={(event) => setQuery(event.target.value)} />
<p>Найдено: {found.length}</p>
</>
);
}Первый аргумент — функция, которая вычисляет значение, второй — зависимости, как у useEffect. Пока posts и query прежние, React вернёт сохранённый массив, не вызывая filter. Введите «qui» — счётчик покажет, сколько заголовков содержат эти буквы.
useCallback: запомнить функцию
Функция, объявленная в компоненте, при каждой отрисовке создаётся заново. useCallback возвращает одну и ту же функцию, пока не изменились зависимости:
// src/App.jsx
import { useCallback, useState } from 'react';
export default function App() {
const [todos, setTodos] = useState([]);
const handleDelete = useCallback((id) => {
setTodos((current) => current.filter((todo) => todo.id !== id));
}, []);
return <TodoList todos={todos} onDelete={handleDelete} />;
}Сеттер вызван с функцией, поэтому todos в зависимостях не нужен, и handleDelete создаётся один раз. Это важно, только если TodoList обёрнут в memo (урок о производительности) или функция стоит в зависимостях эффекта.
Когда это нужно
| Ситуация | Нужна мемоизация? |
|---|---|
| Фильтр или сумма по сотне элементов | Нет |
| Тяжёлое вычисление: тысячи элементов, сложная сортировка | useMemo |
Функция передаётся компоненту, обёрнутому в memo | useCallback |
Функция или объект в зависимостях useEffect | useMemo или useCallback |
| Обычный обработчик для кнопки | Нет |
Сначала пишите без них. Если страница тормозит, найдите медленное место инструментом Profiler в React Developer Tools и только тогда добавляйте мемоизацию.
React Compiler
Существует React Compiler — инструмент сборки, который сам расставляет мемоизацию в компонентах. В проектах, где он подключён, useMemo и useCallback почти не пишут вручную. В обычном шаблоне Vite он не включён, поэтому знать эти хуки всё равно полезно — они часто встречаются в чужом коде.
Частая ошибка
Забытая зависимость заставляет useMemo возвращать устаревшее значение:
// Неправильно: query не в зависимостях, поиск не реагирует на ввод.
const found = useMemo(
() => posts.filter((post) => post.title.includes(query)),
[posts],
);Перечисляйте все значения, которые используются внутри функции. Линтер проекта подскажет, если какое-то пропущено.