Главная / Статьи / Удаление ссылки на последнее значение: события влияния с useEffectEvent

Удаление ссылки на последнее значение: события влияния с useEffectEvent

Узнайте, как функция useEffectEvent в React 19.2 разделяет нереактивную логику от эффекта, устраняет необходимость в хаках с ref latest-value и определяет, когда этот хук является неподходящим инструментом.

917 слов

Во многих проектах на 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.
    • Для каждой зависимости определите, должно ли изменение перезапускать эффект; если нет, то код, который читает это значение, представляет собой логику обработки событий.
  • Этот API довольно мал, но он называет проблему, с которой сталкивается практически каждый разработчик React, не зная, как её назвать.
  • Связанные статьи