Подписки (Subscriptions) и внешние события

Hyperapp предоставляет механизм подписок, который позволяет приложению реагировать на внешние события и интегрироваться с асинхронными источниками данных. Подписки представляют собой функции, которые вызываются после рендера и могут инициировать новые действия (actions) при наступлении определённых событий.

Основные концепции

Подписка — это функция с сигнатурой (dispatch, props) => cleanup, где:

  • dispatch — функция, используемая для отправки действий в приложение.
  • props — объект с параметрами подписки, передаваемый при её подключении.
  • cleanup — необязательная функция для удаления подписки при уничтожении компонента или прекращении действия.

Подписки позволяют работать с:

  • событиями DOM, например keydown, scroll, resize;
  • таймерами (setInterval, setTimeout);
  • WebSocket и SSE;
  • сторонними библиотеками и API.

Подключение подписки

Подписки добавляются в объект subscriptions при инициализации приложения:

import { app } from "https://unpkg.com/hyperapp@2.0.15?module";

const state = { count: 0 };

const actions = {
  increment: () => state => ({ count: state.count + 1 }),
  decrement: () => state => ({ count: state.count - 1 })
};

const subscriptions = (state) => [
  [intervalSubscription, { interval: 1000 }]
];

const intervalSubscription = (dispatch, { interval }) => {
  const id = setInterval(() => dispatch(actions.increment), interval);
  return () => clearInterval(id); // cleanup
};

app({ init: state, view: state => <h1>{state.count}</h1>, node: document.body, actions, subscriptions });

В этом примере подписка intervalSubscription вызывает действие increment каждую секунду. Функция возвращает функцию cleanup, которая очищает таймер при удалении подписки.

Подписки с внешними событиями

Подписки особенно полезны для отслеживания внешних событий, например, нажатий клавиш или изменения размера окна:

const keySubscription = (dispatch) => {
  const handler = e => {
    if (e.key === "ArrowUp") dispatch(actions.increment);
    if (e.key === "ArrowDown") dispatch(actions.decrement);
  };
  window.addEventListener("keydown", handler);
  return () => window.removeEventListener("keydown", handler);
};

const subscriptions = state => [
  keySubscription
];

Особенности:

  • Подписка активна до тех пор, пока приложение работает или пока она не будет заменена.
  • Функция cleanup гарантирует корректное удаление слушателей, предотвращая утечки памяти.
  • Можно использовать несколько подписок одновременно — Hyperapp вызывает каждую из них отдельно.

Параметризация подписок

Подписки могут принимать параметры, что делает их повторно используемыми:

const resizeSubscription = (dispatch, { threshold }) => {
  const handler = () => {
    if (window.innerWidth < threshold) dispatch(actions.decrement);
    else dispatch(actions.increment);
  };
  window.addEventListener("resize", handler);
  return () => window.removeEventListener("resize", handler);
};

const subscriptions = state => [
  [resizeSubscription, { threshold: 800 }]
];

Параметры передаются в подписку как массив [fn, props], что позволяет использовать одну функцию подписки с разными конфигурациями.

Асинхронные подписки

Подписки могут работать с асинхронными источниками данных, например WebSocket или Fetch API. Для WebSocket:

const wsSubscription = (dispatch) => {
  const socket = new WebSocket("wss://example.com/socket");
  socket.addEventListener("message", event => dispatch(actions.receiveMessage(JSON.parse(event.data))));
  return () => socket.close();
};

const actions = {
  receiveMessage: (message) => state => ({ ...state, message })
};

const subscriptions = state => [wsSubscription];

Здесь каждое входящее сообщение инициирует действие receiveMessage, обновляя состояние приложения. Функция cleanup закрывает соединение при уничтожении подписки.

Сочетание подписок с состоянием

Подписки могут реагировать на изменения состояния, если объект subscriptions возвращает динамически вычисляемый массив:

const subscriptions = (state) => state.enabled ? [[intervalSubscription, { interval: 1000 }]] : [];

Таким образом, подписка автоматически отключается, когда состояние изменяет условие, что позволяет динамически управлять активностью внешних слушателей.

Практические рекомендации

  • Всегда возвращать функцию cleanup для предотвращения утечек ресурсов.
  • Разделять подписки по типам событий для упрощения поддержки.
  • Избегать сложной логики внутри подписки — лучше делегировать её действиям.
  • Использовать параметризацию для повторного использования одной подписки в разных местах приложения.
  • Следить за частотой вызова действий при подписках на часто изменяющиеся события (например, scroll) с помощью throttling или debouncing.

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