Перейти к основному содержимому

[Medium] React useEffect и Virtual DOM

1. Что такое useEffect?

Что делает useEffect в React?

useEffect выполняет побочные эффекты после того, как React зафиксирует обновления UI.

Типичные побочные эффекты:

  • Получение удалённых данных
  • Подписка на события браузера
  • Синхронизация с API, такими как document.title или localStorage
  • Запуск и очистка таймеров
import { useEffect, useState } from 'react';

export default function CounterTitle() {
const [count, setCount] = useState(0);

useEffect(() => {
document.title = `Счётчик: ${count}`;
}, [count]);

return (
<button type="button" onClick={() => setCount((v) => v + 1)}>
Клик {count}
</button>
);
}

2. Как работает массив зависимостей?

Без массива зависимостей

Выполняется после каждого рендера.

useEffect(() => {
console.log('выполняется после каждого рендера');
});

Пустой массив зависимостей []

Выполняется один раз при монтировании и очистка при размонтировании.

useEffect(() => {
console.log('только при монтировании');
return () => console.log('очистка при размонтировании');
}, []);

Конкретные зависимости

Выполняется при монтировании и при изменении любого из указанных значений.

useEffect(() => {
console.log('query изменился:', query);
}, [query]);

3. Почему важна очистка

Верните функцию из useEffect для освобождения ресурсов.

useEffect(() => {
const onResize = () => console.log(window.innerWidth);
window.addEventListener('resize', onResize);

return () => {
window.removeEventListener('resize', onResize);
};
}, []);

Без очистки возможны утечки слушателей, таймеров или устаревших асинхронных операций.

4. Распространённые ошибки useEffect

1) Бесконечные перерисовки

// плохо: обновляет состояние при каждом рендере
useEffect(() => {
setCount((v) => v + 1);
});

Исправление: добавить правильный массив зависимостей или перенести логику в другое место.

2) Пропущенные зависимости

Если вы используете переменную внутри эффекта, включите её в зависимости, если она не является намеренно стабильной.

3) Устаревшие замыкания

Эффекты захватывают значения из того рендера, в котором были созданы. Используйте refs или обновление зависимостей при необходимости.

4) Производные вычисления в эффекте

Если значение можно вычислить напрямую из props/state, предпочтительнее useMemo или простое вычисление вместо useEffect.

5. Что такое Virtual DOM?

Virtual DOM — это представление UI в памяти. React сравнивает предыдущее и следующее виртуальные деревья, затем обновляет только необходимые части реального DOM.

Почему это помогает

  • Уменьшает ручные обновления DOM
  • Обеспечивает предсказуемые обновления UI на основе состояния
  • Использует эвристики согласования для эффективности

Это не всегда «бесплатно»; создание и сравнение деревьев тоже имеет стоимость.

6. Согласование (Reconciliation) простыми словами

При изменении состояния:

  1. React строит новое виртуальное дерево
  2. React сравнивает его с предыдущим деревом
  3. React фиксирует минимальные изменения в реальном DOM

Ключи в списках критически важны, потому что помогают React правильно сопоставить старые и новые узлы.

{items.map((item) => (
<li key={item.id}>{item.label}</li>
))}

Стабильные ключи предотвращают некорректное переиспользование и ненужные перемонтирования.

7. Взаимосвязь useEffect и Virtual DOM

  • Virtual DOM diffing происходит во время фазы рендеринга/согласования
  • useEffect выполняется после фазы фиксации
  • Поэтому useEffect в большинстве случаев не блокирует отрисовку

Если необходимо синхронно прочитать/записать layout перед отрисовкой, осторожно используйте useLayoutEffect.

8. Практические паттерны

Загрузка данных с защитой от отмены

useEffect(() => {
let cancelled = false;

async function load() {
const res = await fetch('/api/user');
const data = await res.json();
if (!cancelled) setUser(data);
}

load();

return () => {
cancelled = true;
};
}, []);

Вынос переиспользуемой логики эффектов в пользовательские хуки

function useDocumentTitle(title: string) {
useEffect(() => {
document.title = title;
}, [title]);
}

9. Быстрые ответы для собеседования

В1: Является ли useEffect эквивалентом методов жизненного цикла классов?

Концептуально да, для побочных эффектов, но ментальная модель — «после рендера» с повторными запусками на основе зависимостей.

В2: Означает ли Virtual DOM, что обновления React всегда быстрые?

Не всегда. Производительность по-прежнему зависит от структуры компонентов, частоты рендеринга, мемоизации и стабильности ключей.

В3: Когда следует избегать useEffect?

Избегайте его для чистых производных значений и обработчиков событий, не требующих внешней синхронизации.