В чем разница между useMemo и useEffect?
Хуки
Оба принимают колбэк и зависимости, но делают противоположное: useMemo вычисляет значение во время рендера, useEffect выполняет побочный код после рендера.
Суть каждого
// useMemo: СИНХРОННО вычислить и закешировать ЗНАЧЕНИЕ для рендера
const visible = useMemo(
() => items.filter((i) => i.status === filter),
[items, filter],
);
// useEffect: ПОСЛЕ рендера выполнить ПОБОЧНЫЙ ЭФФЕКТ
useEffect(() => {
analytics.track('filter_changed', { filter });
}, [filter]);Отличия
- Время выполнения: useMemo — во время рендера, синхронно (результат нужен для JSX прямо сейчас); useEffect — после коммита и отрисовки.
- Возврат: useMemo возвращает значение, используемое в рендере; useEffect ничего не возвращает (кроме cleanup-функции).
- Чистота: колбэк useMemo обязан быть чистым (никаких запросов, подписок, setState); useEffect существует именно для «грязной» работы.
- Cleanup: есть только у useEffect.
- Назначение: оптимизация вычислений против синхронизации с внешним миром.
Типовая ошибка — путать их роли
// ❌ производное значение через effect + state: лишний цикл рендера
const [visible, setVisible] = useState([]);
useEffect(() => { setVisible(items.filter(f)); }, [items]);
// ✅ производное значение — это вычисление: useMemo (или просто в рендере)
const visible = useMemo(() => items.filter(f), [items]);И наоборот: запрос данных в useMemo — нарушение чистоты рендера; место запросам — в эффекте (или React Query).
Коротко для собеседования
useMemo — синхронное кеширование вычисленного значения для рендера (чистая функция); useEffect — побочные эффекты после рендера (запросы, подписки, DOM) с cleanup. Правило: производные данные — useMemo, взаимодействие с внешним миром — useEffect; подмена одного другим — распространённый антипаттерн.