Концепция состояния (State) и его управление

Hyperapp — это минималистичный фронтенд-фреймворк, который строится вокруг идеи единого источника правды — состояния приложения. Состояние (state) в Hyperapp представляет собой простой JavaScript-объект, который описывает всю текущую информацию интерфейса. Любые изменения в интерфейсе происходят исключительно через обновление этого состояния, что обеспечивает предсказуемость и прозрачность работы приложения.

Основы состояния

Состояние объявляется при создании приложения через объект state. Пример:

import { h, app } from "hyperapp";

const state = {
  count: 0
};

const view = (state, actions) => (
  <div>
    <h1>{state.count}</h1>
    <button oncl ick={actions.increment}>+</button>
    <button oncl ick={actions.decrement}>-</button>
  </div>
);

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

app({ state, actions, view, root: document.body });

Ключевые моменты:

  • state — объект с данными приложения.
  • Изменение состояния выполняется только через функции actions.
  • View всегда рендерится на основе текущего состояния.

Иммутабельность состояния

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

  • Легко отслеживать изменения.
  • Реализовать функциональное обновление интерфейса.
  • Использовать инструменты отладки, такие как time-travel debug.

Пример обновления состояния с иммутабельностью:

const actions = {
  toggle: () => (state) => ({ ...state, active: !state.active })
};

Оператор ...state создает новый объект с копией всех полей, а active: !state.active обновляет конкретное свойство.

Функции действий (Actions)

Actions в Hyperapp — это функции, которые принимают аргументы и возвращают либо новый объект состояния, либо функцию, которая модифицирует состояние. Существуют два способа определения:

  1. Простое действие — возвращает объект с изменениями:
const actions = {
  increment: () => ({ count: state.count + 1 })
};
  1. Функция с текущим состоянием — более гибкий подход:
const actions = {
  increment: () => (state) => ({ count: state.count + 1 })
};

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

Сложные структуры состояния

Hyperapp позволяет управлять вложенными объектами и массивами. Для обновления вложенного состояния используют глубокое копирование или функции-помощники:

const state = {
  user: { name: "Alice", age: 25 },
  todos: [{ text: "Learn Hyperapp", done: false }]
};

const actions = {
  setUserName: (name) => (state) => ({
    user: { ...state.user, name }
  }),
  toggleTodo: (index) => (state) => ({
    todos: state.todos.map((todo, i) =>
      i === index ? { ...todo, done: !todo.done } : todo
    )
  })
};

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

  • Для вложенных объектов необходимо создавать новый объект с обновленными полями.
  • Для массивов предпочтительно использовать методы map, filter или concat, чтобы не мутировать исходный массив.

Состояние и эффекты (Effects)

Hyperapp позволяет связывать изменение состояния с побочными эффектами через эффекты. Эффект — это функция, которая может выполнять асинхронные операции, сетевые запросы или работу с локальным хранилищем. Пример:

const actions = {
  fetchData: () => (state, actions) => [
    state,
    (dispatch) => fetch("/api/data")
      .then(res => res.json())
      .then(data => dispatch(actions.setData, data))
  ],
  setData: (data) => (state) => ({ data })
};

Важно: эффекты не изменяют состояние напрямую. Они возвращают новые данные через действия, что сохраняет предсказуемость приложения.

Локальное и глобальное состояние

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

const state = {
  counter: { value: 0 },
  userForm: { name: "", email: "" }
};

const actions = {
  counter: {
    increment: () => (state) => ({
      counter: { ...state.counter, value: state.counter.value + 1 }
    })
  },
  userForm: {
    setName: (name) => (state) => ({
      userForm: { ...state.userForm, name }
    })
  }
};

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

Выводы по управлению состоянием

  • Состояние — единый источник правды, отражающий текущее состояние интерфейса.
  • Изменения состояния происходят исключительно через actions.
  • Иммутабельность обеспечивает предсказуемость и простоту отладки.
  • Вложенные объекты и массивы обновляются через создание новых структур.
  • Эффекты отделены от чистого изменения состояния.
  • Структурирование состояния позволяет масштабировать приложения без потери контроля.

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