Головна / Статті / Видалення Referencе «Latest-Value»: Ефекти подій за допомогою useEffectEvent

Видалення Referencе «Latest-Value»: Ефекти подій за допомогою 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 зберігає функцію, яку ви викликаєте з ефекту, так, щоб вона посилалася на закриття останнього рендеру, і тепер намір стає зрозумілим.

Що ви отримуєте

  • Більше немає застарілих закриттів. Ефект подій читає найновіші значення без необхідності вручну оновлювати ref.
  • Комірні повторні виконання. Ефект активується лише тоді, коли змінюється значення, з яким він справді синхронізується.
  • Чіткіша ментальна модель. Ефекти означають „підтримувати цю систему у синхронності з цим станом“; події ефектів означають „виконати це, коли трапиться те“.
  • У документації React з часів версії 18 рекомендувалося роз’єднувати події від ефектів; версія 19.2 нарешті підтримує цю пораду за допомогою стабільного хука. Щодо інших нововведень цієї версії, дивіться наш огляд функцій Activity, useEffectEvent та статичного відображення у React 19.2.

    Перш ніж почати використовувати

    useEffectEvent не замінює useEffect, і це не є універсальним способом скорочення масивів залежностей.

    • Використовуйте його лише тоді, коли зміна значення не повинна перезапускати ефект. Якщо це має статися, значення потрібно розмістити в масиві.
    • Ніколи не викликайте Effect Event під час відображення; ініціюйте його зсередини ефектів, наприклад, через функції-відповідачі або таймери.
    • Зберігайте його локально у компоненті, замість того щоб передавати його дочірнім елементам як проп.
    • Якщо більша частина ефекту знаходиться всередині Effect Event, логіка, ймовірно, має бути у звичайному обробнику подій.

    До версії 19.2 цей хук існував лише у експериментальних версіях, тому проекти на версіях 19.0 або 19.1 повинні оновитися, щоб використовувати стабільний API.

    Основні висновки

    • Кожен спосіб «зберігання останнього значення у ref» є кандидатом на використання з useEffectEvent.
    • Для кожної залежності запитайте себе, чи має зміна перезапускати ефект; якщо ні, код, який її читає, є логікою події.
  • API є дуже простим, але він називає проблему, з якою стикається майже кожен розробник React, не знаючи, як її назвати.
  • Пов’язана література