Создание глобальной системы уведомлений с помощью superstate и React

Приветствую вас, разработчики!

Я хотел бы показать вам свой вариант создания простой глобальной системы уведомлений с помощью superstate и React.

У нас есть одна дополнительная, неявная цель: построить что-то с удовлетворительной эргономикой и удобством для разработчиков.

Без лишних слов,


Если вы предпочитаете, есть также видео этого руководства!


Предварительные условия

Я собираюсь создать совершенно новое приложение create-react-app с TypeScript:

yarn create react-app superstate-notifications --template typescript
Вход в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что я использую yarn, но вы можете подражать моим командам, используя npm.

Когда все готово, давайте переместим наш рабочий каталог в приложение superstate-notifications, которое мы только что создали:

cd superstate-notifications
Вход в полноэкранный режим Выйти из полноэкранного режима

А затем установим superstate:

yarn add @superstate/core
Войти в полноэкранный режим Выход из полноэкранного режима

Круто. Теперь у нас есть готовый проект.

Что такое superstate?

Вкратце, superstate — это библиотека управления микросостояниями для JavaScript-приложений. Несмотря на нюансы, вы можете думать о ней как об альтернативном решении для Redux или Zustand.

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

Начало работы

Теперь, когда у вас есть рабочий проект, давайте создадим файл notifications.tsx в src/ и загрузим состояние наших уведомлений:

import { superstate } from '@superstate/core'

const notifications = superstate([])
Вход в полноэкранный режим Выход из полноэкранного режима

Обратите внимание на [] в superstate(). Это начальное значение вашего состояния. Это как если бы вы набрали:

const notifications = []
Войти в полноэкранный режим Выйти из полноэкранного режима

За исключением того, что вы обернули пустой массив внутри суперсостояния, и это дает нам полномочия.

Создание и уничтожение уведомлений

Следующий шаг — создание двух самых важных функций функции уведомлений: notify и destroy. Соответственно, одна из них предназначена для выпуска новых уведомлений, а другая — для их уничтожения.

Вот что я придумал:

function notify(message: string) {
  const id = Math.random().toString()

  notifications.set((prev) => [...prev, { id, message }])
}

function destroy(id: string) {
  notifications.set((prev) => prev.filter((p) => p.id !== id))
}
Войти в полноэкранный режим Выход из полноэкранного режима

Функция notify.

Она ожидает получить аргумент message (типа string). Это сообщение — то, что увидит пользователь, когда появится уведомление.

Также эта функция объявляет переменную id и присваивает ей Math.random().toString(). Это просто потому, что мы хотим, чтобы наша система поддерживала несколько уведомлений одновременно, и у нас должен быть способ отличить одно уведомление от другого — id является таким способом.

Более того, функция notify вызывает .set() из нашего объекта notifications. Если вы прокрутите немного вверх, то заметите, что объект notifications является нашей переменной superstate(), поэтому .set() — это функция, возвращаемая из него.

Сначала это может показаться сложным, но все, что мы делаем, это передаем в .set() функцию, которая возвращает, как должен выглядеть список уведомлений после того, как мы выпустим новое уведомление.

prev — это предыдущее значение notifications. Изначально значение notifications равно [] (пустой массив), но по мере того, как мы начнем выпускать уведомления, этот массив будет расти, поэтому prev гарантирует, что мы добавляем новые уведомления, а не заменяем их.

Посмотрите, что мы делаем снова:

notifications.set((prev) => [...prev, { id, message }])
Вход в полноэкранный режим Выходим из полноэкранного режима

Это означает, что следующее значение notifications — это прежние уведомления плюс новое, которое представлено объектом со свойствами id и message.

Функция destroy.

Здесь мы говорим, что следующим значением notifications будут все уведомления, кроме того, которое соответствует указанному id, переданному через аргумент функции destroy:

notifications.set((prev) => prev.filter((p) => p.id !== id))
Войти в полноэкранный режим Выход из полноэкранного режима

Уведомления о рендеринге

Теперь в этом же файле notifications.tsx создадим рендерер уведомлений. Его работа очень важна: отображение уведомлений пользователю.

Вот его загрузка:

export function NotificationsRenderer() {
  useSuperState(notifications)

  return null
}
Вход в полноэкранный режим Выход из полноэкранного режима

Подождите, что? Откуда взялась эта функция useSuperState()?

Да, я не упоминал о ней до сих пор. Намеренно. Чтобы интегрировать superstate в React, вам придется установить дополнительную зависимость:

yarn add @superstate/react
Вход в полноэкранный режим Выход из полноэкранного режима

И импортировать ее в ваш файл notifications.tsx:

import { useSuperState } from '@superstate/react'
Войти в полноэкранный режим Выйти из полноэкранного режима

Хук useSuperState перерисовывает наш компонент (NotificationsRenderer) каждый раз при изменении переданного ему состояния. В нашем контексте, это «состояние, переданное ему» относится к notifications.

Вот что я придумал, чтобы сделать рендерер полностью функциональным:

export function NotificationsRenderer() {
  useSuperState(notifications)

  if (!notifications.now().length) {
    return null
  }

  return (
    <div>
      {notifications.now().map((n) => {
        return (
          <div key={n.id}>
            <p>{n.message}</p>

            <button onClick={() => destroy(n.id)}>
              Destroy
            </button>
          </div>
        )
      })}
    </div>
  )
}
Войти в полноэкранный режим Выход из полноэкранного режима

Давайте разделим это на части:

if (!notifications.now().length) {
  return null
}
Вход в полноэкранный режим Выход из полноэкранного режима

Приведенное выше if гарантирует, что при отсутствии уведомлений ничего не будет отображаться. Обратите внимание на метод now() — он возвращает текущее значение массива notifications. Условие гласит, что если в списке notifications нет элементов, то мы хотим отобразить null.

{notifications.now().map((n) => {
Вход в полноэкранный режим Выход из полноэкранного режима

Строка выше будет перебирать каждый элемент в массиве notifications и возвращать что-то. В нашем контексте, для каждого уведомления будет отрисовано что-то. Обратите внимание, что now() снова присутствует.

return (
  <div key={n.id}>
    <p>{n.message}</p>

    <button onClick={() => destroy(n.id)}>
      Destroy
    </button>
  </div>
)
Вход в полноэкранный режим Выход из полноэкранного режима

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

В качестве последнего кусочка головоломки рендеринга откроем ./src/App.tsx и очистим возвращаемый компонент, чтобы он выглядел примерно так:

export default function App() {
  return ()
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда в доме чисто, мы можем приступить к рендерингу:

import { NotificationsRenderer } from './notifications'

export default function App() {
  return (
    <div>
      <NotificationsRenderer />

      <button>Give me a notification!</button>
    </div>
  )
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Выдача уведомлений

Возможно, вы заметили, что мы создали кнопку Give me a notification! в разделе выше, но ничего с ней не сделали. Ну, пока.

Давайте сделаем так, чтобы она выдавала нам уведомление при каждом нажатии на нее:

<button onClick={() => notify('Hello world!')}>
  Give me a notification!
</button>
Войти в полноэкранный режим Выход из полноэкранного режима

Функция notify не будет работать сразу. Сначала нам нужно экспортировать ее. Вернитесь к файлу notifications.tsx и экспортируйте обе функции notify и destroy, добавив ключевое слово export перед ключевым словом function:

export function notify(message: string) {
  const id = Math.random().toString()

  notifications.set((prev) => [...prev, { id, message }])
}

export function destroy(id: string) {
  notifications.set((prev) => prev.filter((p) => p.id !== id))
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, в App.tsx, вы сможете импортировать их:

import {
  notify,
  destroy,
  NotificationsRenderer,
} from './notifications'
Войти в полноэкранный режим Выйти из полноэкранного режима

И бум! Сохраните все ваши файлы и идите в браузер играть с вашей свежей системой уведомлений 🙂

Подведение итогов

Ваш финальный notifications.tsx должен выглядеть следующим образом:

import { superstate } from '@superstate/core'
import { useSuperState } from '@superstate/react'

const notifications = superstate([])

export function notify(message: string) {
  const id = Math.random().toString()

  notifications.set((prev) => [...prev, { id, message }])
}

export function destroy(id: string) {
  notifications.set((prev) => prev.filter((p) => p.id !== id))
}

export function NotificationsRenderer() {
  useSuperState(notifications)

  if (!notifications.now().length) {
    return null
  }

  return (
    <div>
      {notifications.now().map((n) => {
        return (
          <div key={n.id}>
            <p>{n.message}</p>

            <button onClick={() => destroy(n.id)}>
              Destroy
            </button>
          </div>
        )
      })}
    </div>
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

И ваш App.tsx:

import {
  notify,
  destroy,
  NotificationsRenderer,
} from './notifications'

export default function App() {
  return (
    <div>
      <NotificationsRenderer />

      <button onClick={() => notify('Hello world!')}>
        Give me a notification!
      </button>
    </div>
  )
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете посмотреть более сложный пример на StackBlitz:

Заключительные мысли

Это довольно базовая система уведомлений, но довольно мощная и интуитивно понятная. Теперь, чтобы отправлять уведомления в вашем приложении, все, что вам нужно сделать, это вызвать функцию notify(), которую вы создали сами, из любого места вашего приложения, включая код, не относящийся к React, и получать удовольствие, потому что все будет работать.

А теперь идите и повеселитесь, и не стесняйтесь обращаться с любыми вопросами или отзывами! d(^_^)z

Оцените статью
devanswers.ru
Добавить комментарий