useEffectEvent + useEffect = эффекты, которые мы заслужили

Frontend

Недавно на моем проекте мы переехали с 18 версии React на 19. Теперь у нас самый свежий на текущий момент React v19.2. Я занимался этим перед Новым годом в период код-фриза, поэтому было изучить новинки и рассказать о тех что могли бы быть нам полезны коллегам в формате презентации. Об этом опыте я не преминул упомянуть в одном из последних технических интервью.

Интервьюер оказался подготовленным и сразу решил проверить — не вру ли я, и задал задачу, которая предполагала использование нового хука useEffectEvent. Не буду строить из себя гения: я не вспомнил этот хук и решил задачу старыми методами. Сегодня же хочу провести работу над ошибками и поговорить об этом новом, довольно классном и полезном хуке.

Задачу выдумаем свою, чтобы не «сливать» чужие наработки.

Постановка задачи

Представим, что у нас есть компонент для сбора каких-либо метрик. И на каждый клик мыши в документе он должен отправлять событие, в которое полезной нагрузкой добавляется объект с какой-то статистикой, которая приходит строкой. Метод sendAnalytics импортируется откуда-то извне. Импорты и рендер JSX опустим, для описания текущей темы это лишнее.

Решаем по старинке

До React v19.2 мы бы могли написать код для решения описанной проблемы как-то так.

const AnalyticsTracker => ({ data }) {
    useEffect(() => {
        const clickListener = () => sendAnalytics(eventName, { data })
        document.addEventListener('click', clickListener);

        return () => document.removeEventListener('click', clickListener);
    }, [data]);
}

На каждое обновление входящих через пропсы данных мы обязаны отписываться от слушателя события и создавать новый. Метод рабочий, но забудь отписаться и получишь утечку памяти.

На сцену выходит useEffectEvent

Сначала напишу решение в новом стиле, а потом обсудим что получилось

const AnalyticsTracker => ({ data }) {
    const sendEvent = useEffectEvent((eventName) => { sendAnalytics(eventName, { data });

    useEffect(() => {
        const clickListener = () => sendEvent('user_click', { data })
        document.addEventListener('click', clickListener);

        return () => document.removeEventListener('click', clickListener);
    }, []);
}

Что мы сделали. Мы вынесли логику, завязанную на изменяемые данные, из реактивного эффекта в отдельную функцию, которую создали через useEffectEvent. При этом получилось разделение по сути — useEffectEvent занят отправкой аналитики, useEffect — подпиской на событие и отпиской от него.

Итоги

Подробнее, конечно же, читайте в официальной документации. У хука есть ограничения и особенности, которые стоит знать. Я же подытожу так.

  • useEffectEvent — это дополнение к useEffect и использоваться они должны вместе (а также с useLayoutEffect и useInsertionEffect)
  • useEffectEvent позволяет декомпозировать логику, описанную в эффектах, что упрощает их поддержку и дебаггинг
  • useEffectEvent снижает риск уйти в бесконечный рендер, допустить утечку памяти и перегреть устройство пользователя

Словом, useEffectEvent — молодец. И вы, если дочитали до конца, тоже.

Симо Мофин
Добавить комментарий