Hyperapp изначально проектировался как минималистичный фреймворк, однако его модель состояния позволяет строить сложные и глубоко вложенные структуры данных без потери прозрачности и управляемости. Вложенные состояния — ключевой инструмент при разработке нетривиальных интерфейсов, где данные логически сгруппированы и взаимосвязаны.
Состояние в Hyperapp — это обычный JavaScript-объект, передаваемый в
функцию app. Он может содержать любые допустимые структуры:
примитивы, массивы, объекты, вложенные комбинации.
const state = {
user: {
id: 1,
name: "Alex",
settings: {
theme: "dark",
notifications: true
}
},
todos: [
{ id: 1, title: "Learn Hyperapp", done: false }
]
}
Отсутствие специальных классов или прокси делает структуру состояния полностью предсказуемой. Вложенность не имеет технических ограничений, однако накладывает требования к корректному обновлению данных.
Hyperapp использует иммутабельную модель обновления состояния. Это означает, что при изменении вложенного значения необходимо создавать новые объекты на каждом уровне вложенности, затронутом изменением.
Некорректный подход:
state.user.settings.theme = "light"
return state
Корректный подход:
(state) => ({
...state,
user: {
...state.user,
settings: {
...state.user.settings,
theme: "light"
}
}
})
Каждый уровень копируется с помощью оператора расширения
(...), что позволяет Hyperapp корректно определить
изменение состояния и перерисовать интерфейс.
Действия (actions) в Hyperapp могут быть организованы зеркально структуре состояния. Это повышает читаемость и логическую целостность кода.
const actions = {
user: {
setName: (name) => (state) => ({
...state,
name
}),
settings: {
toggleNotifications: () => (state) => ({
...state,
notifications: !state.notifications
})
}
}
}
При такой структуре state внутри действия соответствует
текущему поддереву состояния, а не всему корневому объекту. Это
принципиально важно: вложенные действия получают доступ только к своей
части данных.
Вложенные состояния позволяют моделировать локальное состояние компонентов без выделения отдельных глобальных хранилищ. Компоненты могут принимать поддеревья состояния и соответствующие действия.
const Settings = ({ state, actions }) => (
<div>
<span>{state.theme}</span>
<button oncl ick={actions.toggleTheme}>Toggle</button>
</div>
)
Передача осуществляется на уровне представления:
Settings({
state: state.user.settings,
actions: actions.user.settings
})
Такой подход формирует чёткие границы ответственности и упрощает повторное использование компонентов.
Массивы во вложенных структурах требуют особой аккуратности. Любое изменение элемента массива также должно быть иммутабельным.
Пример обновления элемента списка:
const toggleTodo = (id) => (state) => ({
...state,
todos: state.todos.map(todo =>
todo.id === id
? { ...todo, done: !todo.done }
: todo
)
})
Здесь создаётся новый массив и новый объект только для изменяемого элемента, что минимизирует накладные расходы и сохраняет корректность.
При росте вложенности структуры становятся сложными для поддержки. Часто применяется нормализация — разбиение данных на словари по идентификаторам.
Вместо:
projects: [
{
id: 1,
tasks: [
{ id: 10, title: "Task A" }
]
}
]
Используется:
projects: {
byId: {
1: { id: 1, taskIds: [10] }
}
},
tasks: {
byId: {
10: { id: 10, title: "Task A" }
}
}
Такой подход снижает глубину вложенности, упрощает обновления и уменьшает количество копируемых объектов при изменениях.
Hyperapp не предоставляет встроенных селекторов, однако вычисляемые подструктуры легко реализуются с помощью чистых функций.
const getCompletedTodos = (state) =>
state.todos.filter(t => t.done)
Подобные функции не изменяют состояние и могут свободно использоваться в представлениях и действиях, обеспечивая единый источник логики.
Действия могут возвращать не весь объект состояния, а только изменяемую часть. Hyperapp выполнит слияние автоматически, если действие находится во вложенном пространстве.
const actions = {
counter: {
increment: () => (state) => ({ value: state.value + 1 })
}
}
Здесь возвращается только обновлённое поддерево counter,
без необходимости вручную копировать корневой объект.
Частые проблемы при работе со вложенными структурами:
Эти ошибки приводят к трудноуловимым багам и непредсказуемому поведению интерфейса.
Эффективная работа с вложенными состояниями в Hyperapp опирается на несколько принципов:
Такая архитектура сохраняет минимализм Hyperapp, позволяя при этом строить масштабируемые и поддерживаемые приложения.