Hyperapp строит работу со состоянием вокруг строгой иммутабельности. Состояние рассматривается как неизменяемая структура данных: любое обновление приводит к созданию нового объекта состояния, а не к модификации существующего. Это ключевое условие корректной работы диффинга и предсказуемого рендера.
Обновление состояния всегда происходит через actions, которые возвращают новое состояние целиком либо его часть. Даже если меняется одно поле, создаётся новый объект верхнего уровня:
const state = {
count: 0
}
const actions = {
increment: () => state => ({
...state,
count: state.count + 1
})
}
Такой подход:
Actions в Hyperapp представляют собой чистые функции, не зависящие от внешнего контекста. Они получают текущее состояние и возвращают новое. Это позволяет рассматривать обновление состояния как детерминированное преобразование данных.
Простейший паттерн — одно действие = одно изменение:
const actions = {
setName: name => state => ({
...state,
name
})
}
Отсутствие логики вне action-функции гарантирует, что одинаковый вход всегда даёт одинаковый выход. Это облегчает тестирование и упрощает reasoning о поведении приложения.
Для вложенных структур применяется каскадное копирование. Каждому уровню вложенности соответствует новый объект:
const state = {
user: {
profile: {
email: ''
}
}
}
const actions = {
updateEmail: email => state => ({
...state,
user: {
...state.user,
profile: {
...state.user.profile,
email
}
}
})
}
Данный паттерн гарантирует, что:
Для глубоко вложенных структур рекомендуется нормализация состояния, а не усложнение копирования.
Крупное состояние логически делится на независимые сегменты. Каждому сегменту соответствует собственный набор actions. Hyperapp поддерживает композицию действий через вложенные объекты:
const state = {
counter: 0,
todos: []
}
const actions = {
counter: {
increment: () => state => state + 1
},
todos: {
add: text => state => state.concat({ text, done: false })
}
}
Каждый action получает только свой срез состояния. Это снижает связанность и делает код масштабируемым. Такой паттерн особенно эффективен в крупных приложениях, где разные части состояния развиваются независимо.
Асинхронные операции и побочные эффекты выносятся в effects, которые не изменяют состояние напрямую. Effect возвращает действие, которое будет применено после завершения операции.
const actions = {
loadData: () => [
state => ({ ...state, loading: true }),
fetchEffect
],
setData: data => state => ({
...state,
loading: false,
data
})
}
Здесь обновление состояния остаётся чистым, а побочные эффекты изолированы. Паттерн «action + effect» предотвращает смешение логики данных и ввода-вывода.
Hyperapp допускает возврат массива действий, которые применяются последовательно. Это полезно для сложных сценариев, где одно логическое событие приводит к нескольким изменениям состояния.
const actions = {
submit: () => [
state => ({ ...state, submitting: true }),
sendFormEffect
]
}
Каждый шаг остаётся атомарным и предсказуемым. Такой паттерн предпочтительнее монолитных action-функций с большим количеством логики.
Состояние должно хранить только первичные данные. Любые значения, которые могут быть вычислены, не сохраняются напрямую. Производные данные формируются на этапе рендера или через вспомогательные функции.
Неправильно:
{
items: [...],
completedCount: 3
}
Правильно:
const completedCount = state.items.filter(i => i.done).length
Этот паттерн предотвращает рассинхронизацию данных и упрощает обновления состояния, так как меняется только источник истины.
Для сброса состояния используется возвращение исходного объекта или его копии. Начальное состояние выносится в отдельную константу:
const initialState = {
count: 0
}
const actions = {
reset: () => () => initialState
}
Это гарантирует корректное восстановление состояния без накопления побочных изменений и облегчает реализацию сценариев повторной инициализации.
Actions должны заниматься исключительно трансформацией состояния. Любая бизнес-логика, не влияющая напрямую на форму данных, выносится во внешние функции. Это снижает сложность и делает паттерны обновления состояния прозрачными.
const calculateTotal = items =>
items.reduce((sum, i) => sum + i.price, 0)
const actions = {
updateItems: items => state => ({
...state,
items,
total: calculateTotal(items)
})
}
Логика остаётся переиспользуемой, а action выполняет единственную ответственность — формирование нового состояния.
Все паттерны обновления состояния в Hyperapp направлены на достижение строгой предсказуемости: состояние меняется только в одном месте, по понятным правилам и без скрытых эффектов. Это позволяет рассматривать приложение как цепочку преобразований данных, где каждый шаг можно анализировать, тестировать и оптимизировать независимо.