Механизм подписок в Hyperapp

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

Подписки не изменяют напрямую состояние приложения. Вместо этого они отправляют действия (actions), которые обновляют состояние через стандартный поток Hyperapp: состояние → view → action → новое состояние.


Синтаксис и структура подписок

Подписки задаются в корневой функции app в объекте subscriptions. Каждая подписка — это функция, принимающая текущее состояние и возвращающая функцию, которая вызывает переданные действия.

Простейший пример подписки:

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

const state = { count: 0 };

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

const subscriptions = state => [
  (dispatch) => {
    const interval = setInterval(() => dispatch(actions.increment), 1000);
    return () => clearInterval(interval);
  }
];

app({ state, actions, view: state => h("div", {}, state.count), subscriptions });

Разбор:

  • Функция подписки принимает dispatch как аргумент.
  • Подписка может создавать внешние ресурсы (например, setInterval).
  • Возвращаемая функция служит для очистки ресурсов, когда подписка больше не нужна. Это предотвращает утечки памяти.

Типичные сценарии использования подписок

  1. Таймеры и интервалы

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

  1. События DOM

Подписки могут слушать события на элементах, которые создаются вне Hyperapp:

const subscriptions = () => [
  (dispatch) => {
    const handleClick = () => dispatch(actions.increment);
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
  }
];
  1. WebSocket и работа с API

Подписки идеально подходят для подписки на внешние потоки данных. Например:

const subscriptions = () => [
  (dispatch) => {
    const ws = new WebSocket("wss://example.com/socket");
    ws.onmess age = event => dispatch(actions.updateData(JSON.parse(event.data)));
    return () => ws.close();
  }
];

Управление жизненным циклом подписок

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

  • Предотвращения утечек памяти
  • Отписки от событий
  • Корректного закрытия соединений

Пример с динамическими подписками:

const state = { enabled: true };

const actions = {
  toggle: () => state => ({ enabled: !state.enabled }),
  increment: () => state => ({ count: (state.count || 0) + 1 })
};

const subscriptions = state => state.enabled ? [
  dispatch => {
    const interval = setInterval(() => dispatch(actions.increment), 1000);
    return () => clearInterval(interval);
  }
] : [];

При переключении enabled на false предыдущий интервал автоматически очищается.


Рекомендации по организации подписок

  • Декомпозиция подписок: лучше создавать небольшие подписки для каждой задачи, чем одну универсальную.
  • Чистка ресурсов: всегда возвращать функцию для очистки ресурсов.
  • Избегание побочных эффектов в state/actions: подписки не должны изменять состояние напрямую.
  • Подписки как отдельный слой: логика подписок обычно не смешивается с логикой view и actions.

Взаимодействие подписок и действий

Подписки используют dispatch для вызова действий. Actions могут быть синхронными или асинхронными. Асинхронные действия особенно полезны при работе с внешними API:

const actions = {
  fetchData: () => async state => {
    const response = await fetch("https://api.example.com/data");
    const data = await response.json();
    return { data };
  }
};

const subscriptions = () => [
  dispatch => {
    const timer = setInterval(() => dispatch(actions.fetchData), 5000);
    return () => clearInterval(timer);
  }
];

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


Особенности Hyperapp подписок

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

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