Kodokon kodokon.com

useEffect: эффекты, зависимости, очистка

Синхронизируй свои компоненты с внешним миром через useEffect, освоив зависимости и очистку.

9 мин · 3 вопросов

Открыть этот урок в Kodokon

Компонент React вычисляет JSX из пропсов и состояния. Всё, что выходит за эти рамки - изменить заголовок документа, запустить таймер, подписаться на событие браузера, - это побочный эффект. useEffect выполняет такой код после рендера, не блокируя отображение, и заново синхронизирует его, когда меняются определённые значения.

JSX
import { useEffect } from "react";

function CartTitle({ count }) {
  useEffect(() => {
    document.title = `Cart (${count})`;
  }, [count]);

  return <p>Items: {count}</p>;
}
Эффект перезапускается, только когда меняется count.

Массив зависимостей управляет тем, как часто выполняется эффект. [count]: эффект перезапускается после каждого рендера, в котором изменился count. []: эффект выполняется только один раз, после первого рендера. Без массива: эффект перезапускается после каждого рендера, а это редко то, что тебе нужно. Золотое правило: любое значение компонента, используемое внутри эффекта, должно быть в массиве.

JSX
import { useEffect, useState } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setSeconds((s) => s + 1);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <p>Elapsed: {seconds} s</p>;
}
Без clearInterval таймер пережил бы компонент.

Функция, возвращаемая эффектом, - это функция очистки. React вызывает её перед каждым новым запуском эффекта, а затем последний раз при размонтировании компонента. Именно она предотвращает утечки: осиротевшие таймеры, накопившиеся слушатели, подписки, которые никогда не отменяются. Каждый эффект, который что-то «открывает», обязан это закрыть.

JSX
useEffect(() => {
  function onResize() {
    setWidth(window.innerWidth);
  }
  window.addEventListener("resize", onResize);

  return () => {
    window.removeEventListener("resize", onResize);
  };
}, []);
Подписка на событие resize, отменяемая при размонтировании.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. При useEffect(fn, []) когда выполняется функция fn?
    • Перед каждым рендером компонента
    • Только после первого рендера
    • При каждом обновлении состояния
  2. Когда вызывается функция очистки, возвращаемая эффектом?
    • Только когда компонент размонтируется
    • Перед каждым перезапуском эффекта и при размонтировании
    • Прямо перед первым рендером компонента
    • Никогда автоматически: её нужно вызывать самому
  3. Эффект читает userId, но его массив зависимостей - []. В чём проблема?
    • Эффект не перезапустится при изменении userId и сохранит устаревшее значение
    • React выбросит ошибку при первом рендере
    • Эффект будет перезапускаться в бесконечном цикле