Практически каждый Frontend-разработчик имел дело с событиями. Будь то нажатие кнопки, наведение курсора на форму, нажатие клавиш или любое другое событие по умолчанию, которое предоставляет JavaScript, использование этих событий делает наши веб-страницы производительными, неуправляемыми и довольно простыми в кодировании. Однако мы, как разработчики, не часто используем события в полной мере. То, как JavaScript представляет их нам, — это просто способ заставить наши кнопки запускать функцию (что важно, но немного невообразимо). В этой статье я собираюсь рассказать о нескольких вещах, которые мы можем сделать, создавая и слушая наши собственные пользовательские события.

Мы собираемся настроить приложение React, состоящее из трех компонентов: компонент Counter, компонент SubtractFromCounter и компонент AddToCounter. Мы сможем управлять состоянием счетчика в родительском счетчике, используя события, испускаемые дочерними компонентами сложения и вычитания. Мы также сможем уведомить дочерние компоненты о сбросе их состояния из компонента Counter, просто генерируя некоторые события. Это намного проще, чем передавать ссылки и функции настройки состояния.

СобытиеАвтобус

Нам понадобится шина событий, к которой все наши компоненты имеют доступ, могут публиковать сообщения и прослушивать сообщения, которые на нее помещаются. К счастью для нас, DOM — это отличная шина событий, доступная из любого компонента.

import {useEffect, useState} from "react";

const eventBus = document;

Счетчик

Теперь мы можем создать компонент счетчика.

const Counter = () => {
  const [count, setCount] = useState(0);

  // register our events we want to listen to
  eventBus.addEventListener("custom:add", e => {
      console.log(`Received event: ${e.type}`);
      setCount(count + Number(e.detail.amount));
  });
  eventBus.addEventListener("custom:subtract", e => {
      console.log(`Received event: ${e.type}`);
      setCount(count - Number(e.detail.amount));
  });

  // dispatching event to reset counters
  const dispatchResetCountersEvent = () => {
      const resetInputsEvent = new CustomEvent("custom:reset");
      console.log(`Emitting event: ${resetInputsEvent.type}`);
      eventBus.dispatchEvent(resetInputsEvent);
  }

  return (
      <div>
          <p>Current Counter is: {counter}</p>
          <button onClick={dispatchResetCountersEvent}>Reset Counters</button>

        <AddToCounterComponent />
        <SubtractFromCounterComponent/>
      </div>
  );
}

Мы устанавливаем состояние для нашей переменной count.

Затем мы добавляем прослушиватели для пользовательских событий add и subtract (рекомендуется добавлять к вашим пользовательским событиям custom: чтобы они не конфликтовали с какими-либо событиями по умолчанию, настроенными js). Логика внутри наших обработчиков событий просто увеличивает или уменьшает счетчик на значение, которое мы получаем от события.

Затем мы создаем функцию для отправки нашего пользовательского события сброса, и мы говорим, что каждый раз, когда мы нажимаем кнопку «Сбросить счетчики», мы будем передавать это событие в шину событий.

Аддтокаунтеркомпонент

const AddToCounterComponent = () => {
  const [amount, setAmount] = useState(0);

  useEffect(() => {
      // add listener for reset counter to set amount back to 0
      eventBus.addEventListener("custom:reset", (e) => {
          console.log(`Received event: ${e.type}`);
          setAmount(0);
      })
  }, []);

  // dispatching the add event to our message bus
  const dispatchAddEvent = () => {
    const addEvent = new CustomEvent("custom:add", {detail: {amount: amount}});
    console.log(`Emitting event: ${addEvent.type} ${JSON.stringify(addEvent.detail)}`);
    eventBus.dispatchEvent(addEvent);
  }

  return (
      <div>
        <input value={amount} onChange={e => {setAmount(Number(e.target.value))}} type={"number"}/>
        <button onClick={dispatchAddEvent}>Add</button>
      </div>
  );
}

В нашем AddToCounter у нас есть одно состояние для суммы, которую мы добавим.

Мы используем useEffect без каких-либо зависимостей, чтобы выполнить некоторый код только один раз (при монтировании компонента). В этом коде мы добавляем прослушиватель для нашего события сброса, если мы его услышим, то сбросим состояние суммы.

Затем мы создаем функцию для отправки нашего пользовательского события добавления, и мы говорим, что каждый раз, когда мы нажимаем кнопку «ДОБАВИТЬ», мы будем передавать это событие в шину событий. Мы можем передавать информацию в событии, используя поле подробностей в конструкторе. В этом случае я просто хочу передать сумму для увеличения, которую я получаю от государства.

Вычесть из счетчика компонента

const SubtractFromCounterComponent = () => {
    const [amount, setAmount] = useState(0);

    useEffect(() => {
        // add listener for reset counter to set amount back to 0
        eventBus.addEventListener("custom:reset", (e) => {
            console.log(`Received event: ${e.type}`);
            setAmount(0);
        })
    }, []);

    // dispatching the add event to our message bus
    const dispatchAddEvent = () => {
        const addEvent = new CustomEvent("custom:subtract", {detail: {amount: amount}});
        console.log(`Emitting event: ${addEvent.type} ${JSON.stringify(addEvent.detail)}`);
        eventBus.dispatchEvent(addEvent);
    }

    return (
        <div>
            <input value={amount} onChange={e => {setAmount(Number(e.target.value))}} type={"number"}/>
            <button onClick={dispatchAddEvent}>Subtract</button>
        </div>
    );
}

Вычитание — это то же самое, что и добавление, за исключением того, что вместо этого мы генерируем пользовательское событие вычитания.

Теперь вы можете запустить это веб-приложение и поэкспериментировать с тем, как генерируются и прослушиваются события. Мы увидели очень простой способ обновления состояний кросс-компонентов без необходимости передачи функций состояния или объектов ref. Мы также увидели, как мы можем вызывать два действия только с одним событием (то есть сбрасывать обе суммы с помощью одного пользовательского события сброса). Вы можете использовать настраиваемые события, чтобы повысить уровень своих навыков работы с интерфейсом и разделить компоненты, заставив их взаимодействовать друг с другом через шину событий!