Что происходит в useEffect при передаче пустого массива, отсутствия зависимостей или конкретных зависимостей
Что проверяют
Понимаете ли вы три режима и знаете ли, когда выполняется функция очистки.
Как отвечать
Второй аргумент задаёт, когда эффект запускается заново.
Нет второго аргумента (useEffect(fn)): эффект выполняется после каждого рендера. Полезно редко. Типовая ошибка это запрос данных без массива: ответ обновляет состояние, состояние вызывает рендер, рендер снова запускает эффект, и получается бесконечный цикл.
Пустой массив (useEffect(fn, [])): эффект выполняется один раз после монтирования, очистка при размонтировании. Подходит для подписки, таймера, разовой загрузки. Важная ловушка: эффект замыкает значения первого рендера, и если внутри используется состояние или пропс, он навсегда останется со старым значением. Второе: в режиме StrictMode во время разработки React намеренно монтирует компонент дважды, поэтому эффект и его очистка выполнятся по два раза. Это не баг, а проверка, что очистка написана правильно.
Массив с зависимостями (useEffect(fn, [a, b])): после каждого рендера React сравнивает элементы с прошлыми через Object.is. Хотя бы одно отличие, и сначала выполняется очистка предыдущего эффекта, потом сам эффект. Отсюда: сравнение поверхностное, поэтому объект или функция, созданные в теле компонента, отличаются на каждом рендере и запускают эффект бесконечно. Лечится useCallback, useMemo или переносом значения наружу.
Очистка нужна всему, что переживает рендер: отписка от события, clearInterval, отмена запроса через AbortController. Без неё получаются утечки и обновление состояния размонтированного компонента.
Дополнительно стоит знать, что эффект выполняется после отрисовки, поэтому измерение размеров без мигания делают в useLayoutEffect.
Где спотыкаются
Забывают массив и получают бесконечный цикл.
Ставят пустой массив, чтобы «заглушить» предупреждение линтера, и ловят устаревшее значение.
Кладут в зависимости объект, создаваемый при каждом рендере.
Не пишут очистку для подписок и таймеров.