Удаление ссылки на последнее значение: события влияния с useEffectEvent
Узнайте, как функция useEffectEvent в React 19.2 разделяет нереактивную логику от эффекта, устраняет необходимость в хаках с ref latest-value и определяет, когда этот хук является неподходящим инструментом.
Во многих проектах на React присутствует хук useRef, единственная функция которого — сохранять «последнее значение», чтобы эффект не зависел от него. Этот прием работает, но скрывает истинную цель. В React 19.2 хук useEffectEvent стал стабильным, что позволило сделать эту практику полноценным хуком первого класса. К концу вы сможете разделять реактивную и нереактивную логику внутри одного эффекта, а также понимать, когда данный хук не применим.
Массив зависимостей, который не может победить
Конфликт возникает при долговременных соединениях: чат-комнатах, потоках WebSocket, сессиях аналитики, подписках. Компонент ChatRoom получает параметры roomId и theme. Эффект должен восстанавливать соединение при изменении roomId, но читает значение theme лишь для форматирования уведомления после подключения.
Добавьте theme в массив зависимостей, и переключение на темный режим без причины разрушает соединение. Если его не добавлять, правило проверки кода на ошибки будет жаловаться; если его игнорировать, обратный вызов сохранит значение theme с предыдущей обработки, что является классическим примером устаревшего замыкающего функционала.
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => {
showNotification('Connected!', theme); // stale after theme changes
});
connection.connect();
return () => connection.disconnect();
}, [roomId, theme]); // theme shouldn't trigger a reconnect
}
Решение, которое использовали большинство команд
Обычным способом было копирование значения theme в ref при каждой обработке и чтение значения ref.current внутри обратного вызова. Это работает, но шаг синхронизации легко забыть, и в коде нет ничего, что указывало бы на то, что логика намеренно не является реактивной.
Выделение логики обработки событий с помощью useEffectEvent
useEffectEvent выделяет нереактивную часть эффекта в отдельную функцию. Эта функция всегда получает актуальные значения пропсов и состояния, её вызов никогда не запускает эффект заново, и она не попадает в массив зависимостей.
Далее уведомление переходит в состояние onConnected. Этот эффект лишь устанавливает соединение, регистрирует обработчик, подключается и отключается при очистке.
import { useEffect, useEffectEvent } from 'react';
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme); // always fresh, never a dependency
});
useEffect(() => {
const connection = createConnection(roomId);
connection.on('connected', () => onConnected());
connection.connect();
return () => connection.disconnect();
}, [roomId]); // only roomId matters now
}
В качестве зависимости остается только roomId. Параметр theme не был скрыт от инструмента проверки кода; его просто переклассифицировали. Показ сообщения при установке соединения является реакцией на событие, поэтому это логика обработки событий, а не механизм синхронизации. Изменение темы больше не приводит к повторному подключению, и следующее сообщение по-прежнему отображается в текущей теме.
Концептуально это то же самое, что делал ручной хак с ref: React сохраняет функцию, вызываемую в рамках эффекта, такой, чтобы она указывала на замыкание последнего отрисования, и теперь намерение автора становится очевидным.
Что вы получаете
- Больше нет устаревших замыканий. Механизм Effect Event считывает самые свежие значения без необходимости вручную обновлять ref.
В документации React с момента выхода React 18 рекомендуется разделять события и эффекты; версия 19.2 наконец подкрепляет эту рекомендацию стабильным хуком. Подробнее об остальных нововведениях этой версии см. наш обзор Activity, useEffectEvent и статической отрисовки в React 19.2.
Прежде чем использовать его
useEffectEvent не заменяет useEffect, и это не универсальный способ сокращения массивов зависимостей.
- Используйте его только тогда, когда изменение значения не должно перезапускать эффект. Если же оно должно это делать, то значение следует поместить в массив.
- Никогда не вызывайте Event эффекта во время отрисовки; делайте это изнутри эффектов, например, через обратные вызовы подписок или таймеры.
- Храните его локально в компоненте, а не передавайте как свойство дочерним элементам.
- Если большая часть логики эффекта находится внутри Event эффекта, то такая логика, скорее всего, должна находиться в обычном обработчике событий.
До версии 19.2 этот хук существовал только в экспериментальных сборках, поэтому проекты на версиях 19.0 или 19.1 должны обновиться, чтобы использовать стабильный API.
Основные выводы
- Каждое решение вида «хранить последнее значение в ref» является кандидатом на использование
useEffectEvent. - Для каждой зависимости определите, должно ли изменение перезапускать эффект; если нет, то код, который читает это значение, представляет собой логику обработки событий.
Связанные статьи
- Выбор подходящего инструмента для React: Derive, Handle, Fetch, Defer или Effect — Руководство по принятию решений о замене рефлексивных вызовов useEffect на вычисляемые значения, обработчики событий, слой данных, useTransition, measured useMemo и API React 19.
- Прекратите синхронизацию состояния с useEffect: более безопасный подход в React — Узнайте, почему использование useEffect для синхронизации вычисляемого состояния приводит к конкурентным ситуациям и лишним отрисовкам, и как заменить его на вычисления во время отрисовки с использованием атрибута key.
- Создание простого инструмента useState для понимания снимков состояния, очередей и эффектов — Изучите поведение хуков React, создав небольшой компонент useState, а затем используйте эту модель для объяснения работы очередей обновлений, устаревших снимков состояния, замыканий, очистки эффектов, ссылок и группировки операций.
- Асинхронные формы в React 19 с use, useActionState и useOptimistic — Как функции use(), useActionState, useFormStatus и useOptimistic заменяют ручной контроль загрузки и флагов ошибок в React 19, а также какие подводные камни скрываются за каждым из этих хуков.
- Устранение устаревших зависимостей JavaScript: CRA, Moment, jQuery и другие — Какие устаревшие библиотеки и инструменты JavaScript следует постепенно отказываться использовать, почему каждый из них становится проблемой, чем их заменить, и как не допустить снова ухудшения списка зависимостей.