Создание пользовательских эффектов

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

Структура эффекта

Эффект в Hyperapp — это объект с обязательными полями:

const effect = {
  effect: (dispatch, props) => { /* логика эффекта */ },
  // необязательное поле, для идентификации эффекта при повторных вызовах
  id: props => props.id,
  // аргументы эффекта
  props: {...}
};
  • effect — функция, которая выполняет побочное действие. Принимает два аргумента: dispatch и props. dispatch используется для вызова действий в приложении, а props передаются при создании эффекта.
  • id — уникальный идентификатор эффекта. Позволяет Hyperapp понимать, что эффект уже выполняется и не дублировать его.
  • props — объект с параметрами, доступными внутри функции эффекта.

Создание пользовательского эффекта

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

const LogEffect = message => ({
  effect: (dispatch, props) => {
    console.log(props.message);
  },
  props: { message }
});

Использование эффекта в действии:

const actions = {
  logMessage: message => state => [
    state,
    LogEffect(message)
  ]
};

При вызове actions.logMessage("Привет Hyperapp!") сообщение будет выведено в консоль.

Асинхронные эффекты

Hyperapp поддерживает асинхронные операции в эффектах через async/await или промисы. Пример запроса к API:

const FetchEffect = url => ({
  effect: async (dispatch, props) => {
    try {
      const response = await fetch(props.url);
      const data = await response.json();
      dispatch({ type: "fetchSuccess", data });
    } catch (error) {
      dispatch({ type: "fetchError", error });
    }
  },
  props: { url }
});

Эффект вызывается из действия:

const actions = {
  loadData: url => state => [
    state,
    FetchEffect(url)
  ],
  fetchSuccess: data => state => ({ ...state, data }),
  fetchError: error => state => ({ ...state, error })
};

Повторное использование эффектов

Для повышения читаемости и повторного использования часто создаются фабрики эффектов, возвращающие готовые объекты с параметрами:

const DelayEffect = (ms, action) => ({
  effect: (dispatch) => {
    setTimeout(() => dispatch(action), ms);
  },
  id: () => ms
});

Это позволяет управлять эффектами по идентификатору, предотвращая их дублирование при повторных вызовах с одинаковыми параметрами.

Комбинирование эффектов

Hyperapp позволяет возвращать массив эффектов из одного действия:

const actions = {
  init: () => state => [
    state,
    [
      FetchEffect("/api/data"),
      LogEffect("Инициализация завершена")
    ]
  ]
};

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

Эффекты с внутренним состоянием

Иногда требуется, чтобы эффект хранил состояние между вызовами. Для этого можно использовать замыкания внутри функции эффекта:

const CounterEffect = () => {
  let count = 0;
  return {
    effect: () => {
      count += 1;
      console.log("Вызвано раз:", count);
    },
    id: () => "counter"
  };
};

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

Советы по созданию пользовательских эффектов

  1. Не изменять состояние напрямую — эффекты должны взаимодействовать со state только через dispatch.
  2. Использовать уникальный id для повторяющихся эффектов, чтобы избежать нежелательного дублирования.
  3. Избегать долгих синхронных операций внутри эффекта, чтобы не блокировать рендеринг.
  4. Разделять чистые действия и эффекты — это повышает тестируемость и предсказуемость приложения.
  5. Комбинировать эффекты через массивы для упрощения сложных сценариев с несколькими асинхронными операциями.

Интеграция с другими API

Эффекты Hyperapp легко интегрируются с внешними библиотеками и API:

const LocalStorageEffect = key => ({
  effect: (dispatch, props) => {
    const value = localStorage.getItem(props.key);
    dispatch({ type: "loadFromStorage", value });
  },
  props: { key }
});

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

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