


Функция очистки, возвращённая из useEffect, вызывается перед повторной настройкой эффекта с изменившимися зависимостями и при удалении компонента. Она должна отменять то, что создал конкретный запуск эффекта: подписку, таймер или соединение.
В примере Mentoring.digital компонент показывает состояние выбранного канала. При смене канала старая подписка должна исчезнуть, а новая появиться ровно один раз.
Для учебного API договоримся: feed.subscribe(channelId, callback) возвращает функцию unsubscribe. Она снимает именно созданную подписку. Объект feed передаётся компоненту; сам транспорт здесь не реализован.
import { useEffect, useState } from "react";
export function ChannelStatus({ feed, channelId }) {
const [status, setStatus] = useState("waiting");
useEffect(() => {
setStatus("waiting");
const unsubscribe = feed.subscribe(channelId, setStatus);
return () => unsubscribe();
}, [feed, channelId]);
return <p>{channelId}: {status}</p>;
}
В зависимостях указаны оба значения, используемые для внешней подписки. Если channelId изменился с alpha на beta, React сначала выполняет очистку прежнего эффекта, затем устанавливает подписку beta. Замыкание cleanup хранит unsubscribe от своего запуска.
Замена массива на [] не делает код правильнее. Подписка останется связанной с первоначальным каналом, даже если интерфейс уже показывает другой. Удаление зависимости ради исчезновения предупреждения линтера обычно маскирует несоответствие.
Если родитель создаёт новый feed при каждом рендере, эффект тоже будет пересоздаваться. Сначала выясните, зачем меняется объект. Стабилизировать нужно саму модель владения соединением, а не механически убирать feed из зависимостей.
В режиме разработки StrictMode выполняет дополнительный проверочный цикл настройки и очистки эффекта. Это помогает обнаружить код, после которого остаётся подписка. Исправление состоит в симметричной очистке, а не в флаге, запрещающем второй запуск.
Не рассчитывайте на точное число вызовов subscribe за всю жизнь компонента. Полезнее проверять количество активных подписок после завершения цикла: для данного экземпляра и канала должна оставаться одна. После unmount не должно оставаться ни одной.
Снятие подписки зависит от контракта feed. Если он допускает уже поставленные в очередь callbacks после unsubscribe, понадобится дополнительная защита от устаревшего события. Для fetch часто используют AbortController или признак устаревшего результата, но отмена запроса и отбрасывание ответа решают разные задачи.
Не объявляйте функцию setup как async: она вернёт Promise вместо функции очистки. Асинхронную работу запускают внутри эффекта, сохраняя синхронный возврат cleanup.
Для вычисления значения из props без внешней системы эффект обычно не требуется. Расчёт можно сделать во время рендера. Это уменьшает число переходов состояния и упрощает проверку.
Практику компонентов и жизненного цикла дают курсы React. Конкретную утечку подписок можно разобрать с ментором по React.