Hyperapp — это легковесный фронтенд-фреймворк для построения веб-приложений с минимальным объёмом кода. Его ядро строится на трёх ключевых концепциях: состояние (state), действия (actions) и вью (view). Взаимодействие этих компонентов формирует реактивную архитектуру, обеспечивая обновление интерфейса при изменении состояния.
Состояние приложения представляет собой обычный JavaScript-объект, содержащий все данные, необходимые для рендеринга интерфейса. В Hyperapp состояние иммутабельно, что означает, что любое обновление создаёт новый объект состояния, не изменяя старый.
Пример структуры состояния:
const state = {
count: 0,
user: {
name: "Alex",
loggedIn: false
}
};
Действия — это функции, которые изменяют состояние приложения. Каждое действие принимает текущее состояние и возвращает новое состояние. Hyperapp позволяет использовать как синхронные, так и асинхронные действия.
Пример синхронного действия:
const actions = {
increment: state => ({ count: state.count + 1 }),
login: state => ({ user: { ...state.user, loggedIn: true } })
};
Для асинхронных действий Hyperapp использует эффекты (effects). Эффекты позволяют выполнять операции вне состояния, например, запросы к API:
const actions = {
fetchUser: () => (state, actions) => {
fetch("/api/user")
.then(res => res.json())
.then(data => actions.setUser(data));
},
setUser: (state, user) => ({ user })
};
View — это функция, которая принимает текущее состояние и действия, возвращает виртуальный DOM, описанный с помощью JSX-подобного синтаксиса. Hyperapp использует виртуальный DOM для эффективного обновления реального DOM, минимизируя количество операций.
Пример простого представления:
const view = (state, actions) => (
h("div", {}, [
h("h1", {}, `Счетчик: ${state.count}`),
h("button", { onclick: actions.increment }, "Увеличить")
])
);
Hyperapp реализует собственный виртуальный DOM, который хранит дерево элементов в памяти. При изменении состояния происходит сравнение старого и нового дерева (diffing) с последующим обновлением только изменённых узлов. Этот механизм обеспечивает высокую производительность и минимальные операции с реальным DOM.
Пример VNode:
const vnode = {
tag: "div",
props: { id: "container" },
children: [
{ tag: "h1", props: {}, children: ["Привет, мир!"] }
]
};
Hyperapp использует события как часть VNode. События описываются как функции в свойствах элементов. При изменении состояния события автоматически пересвязываются, что предотвращает утечки памяти и гарантирует корректное поведение интерфейса.
h("button", { onclick: actions.increment }, "Увеличить")
Эффекты в Hyperapp предназначены для работы с побочными действиями: HTTP-запросами, таймерами, localStorage. Эффекты отделены от чистых действий, что поддерживает функциональный стиль и предсказуемость состояния.
Структура эффекта:
const fetchEffect = (dispatch, props) => {
fetch(props.url)
.then(res => res.json())
.then(data => dispatch(props.action, data));
};
const actions = {
loadData: () => (state, actions) => fetchEffect(actions.setData, { url: "/api/data", action: actions.setData }),
setData: (state, data) => ({ data })
};
Hyperapp поощряет композицию действий и компонентов. Состояние и действия можно делить на модули, которые затем объединяются в одно приложение. Это позволяет строить масштабируемые приложения без потери простоты.
Пример модульного состояния:
const counterModule = {
state: { count: 0 },
actions: {
increment: state => ({ count: state.count + 1 })
}
};
const userModule = {
state: { name: "", loggedIn: false },
actions: {
login: state => ({ loggedIn: true })
}
};
const state = { ...counterModule.state, ...userModule.state };
const actions = { ...counterModule.actions, ...userModule.actions };
Hyperapp позволяет использовать два подхода к созданию VNode: JSX и hyperscript (h-функция). Оба подхода эквивалентны, но JSX обеспечивает более компактный и читаемый синтаксис.
Hyperscript:
h("div", {}, [
h("span", {}, "Текст")
])
JSX:
<div>
<span>Текст</span>
</div>
Архитектура Hyperapp строится на принципах чистых функций, иммутабельного состояния и реактивного обновления интерфейса. Комбинация этих подходов делает фреймворк быстрым, компактным и легко масштабируемым, сохраняя простоту для небольших проектов и мощь для сложных приложений.
Каждое обновление состояния инициирует diffing, вызывая пересчёт виртуального DOM и последующее минимальное обновление реального DOM, обеспечивая высокую производительность и предсказуемость поведения приложения.