Простая шина событий на JavaScript

год назад·3 мин. на чтение

Шины событий — это чрезвычайно полезный инструмент для разделения компонентов приложений.

Использование шины событий имеет свои плюсы и минусы. Поэтому она должна добавляться осторожно, иначе вы можете получить код, который будет трудно поддерживать и понимать. Но нет никаких сомнений в том, что шина событий может значительно ускорить процесс прототипирования или улучшить архитектуру малого и среднего приложения. Большому приложению могут потребоваться некоторые дополнительные инструменты и подходы. В этой статье рассмотрим как реализовать простую шину событий в JavaScript.

Что такое шина событий (event bus)?

Шина событий реализует шаблон издатель/подписчик. Его можно использовать для разъединения компонентов приложения, так что компонент может реагировать на события, инициируемые другим компонентом, без их прямых зависимостей друг от друга. Им нужно только знать шину событий. Каждый подписчик может подписаться на определенное событие. Подписчик будет уведомлен, когда событие, на которое он подписан, будет опубликовано в шине событий. Издатель может публиковать события в шине событий, когда что-то происходит.

Реализация шины событий

В этой реализации подписчик (subscriber) является простой функцией. Функция вызывается, когда интересующее событие публикуется в шине событий. Для сопоставления события с подписчиками можно использовать простой объект. Формат данных в этом объекте будет следующим: { eventType: { id: callback } }. Например:
{  
  event1: {
    1: func1,
    2: func2
  },  
  event2: {
    3: func3
  }  
}
События event1 и event2 могут быть любого типа. В большинстве случаев имеет смысл использовать простые строки. Подписчики func1, func2 и func3 являются простыми JavaScript функциями.
func1 и func2 являются подписчиками, подписанными на события типа event1. func3 является подписчиком, подписанным на события типа event2.
Идентификаторы 1, 2 и 3 будут использоваться позже для отмены подписки на подписчиков.

Как подписаться на событие?

Функция subscribe принимает в качестве аргументов интересующее событие и подписчика. Функция возвращает объект, предоставляющий функцию отмены подписки - unsubscribe. Функция unsubscribe может быть вызвана для удаления зарегистрированного подписчика. В этом примере используем генератор id для получения уникальных идентификаторов.
Идентификаторы используются для быстрой идентификации (за время O(1)) подписчика при вызове функции unsubscribe.
Вместо использования генератора id можно рассмотреть реализацию на основе ES6 символов.
const subscriptions = {}
const getNextUniqueId = getIdGenerator()
function subscribe(eventType, callback) {
  const id = getNextUniqueId()

  // создаем новый элемент для eventType
  if(!subscriptions[eventType]) {
    subscriptions[eventType] = {}
  }
 
  // регистрируем функцию обратного вызова
  subscriptions[eventType][id] = callback
  return {
    unsubscribe: () => {
      delete subscriptions[eventType][id]
      if(Object.keys(subscriptions[eventType]).length === 0) {
        delete subscriptions[eventType]
      }
    }
  }
}

Как опубликовать событие?

Функция публикации принимает в качестве аргументов событие и аргументы для подписчиков. Если подписчиков на eventType нет, то просто завершаем функцию. В противном случае перебираются идентификаторы подписчиков, зарегистрированных для eventType, и вызывается каждая функция с предоставленными аргументами.
function publish(eventType, arg) {
  if(!subscriptions[eventType]) {
    return
  }
  Object.keys(subscriptions[eventType])
    .forEach(id => subscriptions[eventType][id](arg))
}

Пример использования

В качестве простого примера предположим, что мы хотим печатать что-то в консоли каждый раз, когда появляется событие типа print. Мы можем подписаться на событие следующим образом:
const subscription = EventBus.subscribe(
  "print",
  message => console.log(`printing: ${message}`)
)
Затем мы можем выпустить событие, подобное этому:
EventBus.publish("print", "some text")
Если в какой-то момент мы захотим прекратить прослушивание события с типом print, мы можем отписаться следующим образом:
subscription.unsubscribe()

Полная реализация

Вот полная реализация этой шины событий. Он может быть легко преобразован в простую функцию Javascript.
// eventBus.js

const subscriptions = {}
const getNextUniqueId = getIdGenerator()

function subscribe(eventType, callback) {
  const id = getNextUniqueId()

  if(!subscriptions[eventType]) {
    subscriptions[eventType] = {}
  }

  subscriptions[eventType][id] = callback

  return { 
    unsubscribe: () => {
      delete subscriptions[eventType][id]
      if(Object.keys(subscriptions[eventType]).length === 0) {
        delete subscriptions[eventType]
      }
    }
  }
}

function publish(eventType, arg) {
  if(!subscriptions[eventType]) {
    return
  }
  
  Object.keys(subscriptions[eventType]).forEach(key => subscriptions[eventType][key](arg))
}

function getIdGenerator() {
  let lastId = 0
  
  return function getNextUniqueId() {
    lastId += 1
    return lastId
  }
}

module.exports = { publish, subscribe }

Обработка форм в React с помощью хуков

год назад·3 мин. на чтение

В этой статье рассмотрим, как обрабатывать формы в приложениях react с помощью хуков.

Формы

Формы позволяют нам принимать данные от пользователей и отправлять их на сервер для обработки. Это различные типы форм, такие как «Вход», «Регистрация» и т.д. В HTML5 есть элементы формы, такие как input, textarea, select, они поддерживают собственное внутреннее состояние в DOM, но в React мы поддерживаем состояние элементов формы внутри компонента, чтобы мы могли иметь полный контроль над элементами формы.

Что такое обработка форм

Обработка форм означает, как мы обрабатываем данные формы, когда пользователь изменяет значение или отправляет форму. Давайте посмотрим на пример того, как мы обрабатываем данные инпута с помощью хуков React.
import React,{useState} from 'react';

function Form() {
  const [name,setName] = useState('');

  function handleNameChange(e){
    setName(e.target.value)
  }

  function handleSubmit(e) {
    e.preventDefault() // останавливаем перезагрузку страницы по умолчанию
    console.log(name);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input placeholder="Name" value={name}
        onChange={handleNameChange}/>
      <button>Submit</button>
    </form>
  )
}
В приведенном выше коде мы установили для атрибута value элемента input свойство name, а метод обработчика событий onChange - handleNameChange - запускается каждый раз, когда мы вводим некоторые данные в элемент input, и он обновляет свойство name с помощью метода setName, так что мы продолжаем синхронизировать значение с состоянием react. handleSubmit используется для отправки формы.

Элемент select

<select> помогает нам создать выпадающий список. Давайте посмотрим на пример того, как создать выпадающий список и обрабатывать данные.
import React,{useState} from 'react';

function FormSelect() {
  // начальное значение состояния - строка 'react'
  const [framework, setFramework] = useState('react');

  function handleChange(e){
    setFramework(e.target.value);
  };

  function handleSubmit(e){
    e.preventDefault();
    console.log(framework);
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>Choose your framework</h2>
      <select onChange={handleChange} value={framework}>
        <option value="react">React</option>
        <option value="angular">Angular</option>
        <option value="vue">Vue</option>
      </select>
      <button type="submit">Submit</button>
    </form>
  );
}
Здесь мы создали выпадающий список фреймворков в элементе <select> мы установили атрибут value для свойства framework и добавили обработчик событий onChange. Вложенные элементы <option> содержат атрибут value, который содержит данные, так что всякий раз, когда мы выбираем конкретный параметр, вызывается метод handleChange и изменяет значение свойства framework значением атрибута <option>. Вы видели, что элемент select также следует аналогичному шаблону, как и элемент input, так почему бы нам не создать пользовательский хук и повторно использовать его в каждом элементе формы?

Создание пользовательских хуков для обработки формы

import React, {useState} from 'react';

function useInput(initialValue) {
  const [value,setValue] = useState(initialValue);

  function handleChange(e) {
    setValue(e.target.value);
  }

  return [value,handleChange];
}
Здесь мы создали пользовательский хук под названием useInput, давайте его применим.

Использование пользовательского хука useInput()

function LoginForm(){

  const [email, setEmail] = useInput('');
  const [password, setPassword] = useInput('');

  function handleSubmit(e){
    e.preventDefault();
    console.log(email,password)
  }

  return (
    <form onSubmit={handleSubmit}>
      <input placeholder="Email" type="email"
        value={email} onChange={setEmail}/>
      <input placeholder="password" type="password"
        value={password} onChange={setPassword}/>
      <button>Submit</button>
    </form>
  )
}
Теперь наш компонент LoginForm выглядит намного чище с помощью пользовательского хука useInput(). Точно так же мы можем использовать наш хук useInput с другими элементами формы.

Пример с Radio button

function RadioButtons() {
  const [data] = useState({male:"male",female:"female",other:"other"})
  const [gender, setGender] = useInput("");
  function handleSubmit(e) {
    e.preventDefault();
    console.log(gender);
  }

  return (
    <form onSubmit={handleSubmit}>
      <h1>Select Your Gender</h1>
      <div>
        <input type="radio" id={data.male} value={data.male}
          checked={data.male === gender} onChange={setGender}/>
        <label htmlFor={data.male}>Male</label>
      </div>
      <div>
        <input type="radio" id={data.female} value={data.female}
          checked={data.female === gender} onChange={setGender}/>
        <label htmlFor={data.female}>Female</label>
       </div>
       <div>
         <input type="radio" id={data.other}  value={data.other}
           checked={data.other === gender} onChange={setGender}/>
         <label htmlFor={data.other}>Other</label>
       </div>
     <button>submit</button>
    </form>
  )
}

Пример элемента Textarea

function Comments() {
  const [comment, setComment] = useInput("");
  function handleSubmit(e) {
    e.preventDefault();
    console.log(comment);
  }

  return(
    <form onSubmit={handleSubmit}>
      <textarea value={comment} onChange={setComment}/>
      <button>submit</button>
    </form>
  )
}