Hyperapp — это минималистичный фреймворк для построения интерфейсов на JavaScript, который опирается на декларативное управление состоянием и виртуальный DOM. Несмотря на небольшие размеры ядра, он легко расширяется и может интегрироваться с разнообразными внешними библиотеками, включая UI-компоненты, утилиты для управления состоянием, анимации и сетевые запросы. Важной особенностью является то, что Hyperapp не навязывает структуру проекта, что делает его совместимым с практически любыми сторонними решениями.
Hyperapp использует виртуальный DOM для управления обновлениями интерфейса. Это накладывает определённые ограничения при работе с библиотеками, которые напрямую манипулируют DOM, например jQuery или D3.js. Основной подход заключается в том, чтобы изолировать такие изменения, обеспечивая их синхронизацию с состоянием Hyperapp.
Пример интеграции D3.js для построения графика:
import { h, app } from "hyperapp";
import * as d3 from "d3";
const state = {
data: [10, 15, 20, 25]
};
const actions = {
setData: (newData) => ({ data: newData })
};
const view = (state, actions) =>
h("svg", { width: 200, height: 100, oncreate: (element) => {
d3.select(element)
.selectAll("rect")
.data(state.data)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", d => 100 - d)
.attr("width", 20)
.attr("height", d => d)
.attr("fill", "teal");
}});
app({ init: state, view, node: document.getElementById("app"), actions });
Ключевой момент: использовать хуки oncreate и
onupdate для выполнения операций с DOM через сторонние
библиотеки, чтобы изменения не конфликтовали с виртуальным DOM
Hyperapp.
Хотя Hyperapp имеет собственную систему управления состоянием, иногда требуется использовать внешние решения, например Redux или Zustand, для синхронизации сложных данных.
Пример синхронизации состояния Hyperapp с Redux:
import { createStore } from "redux";
import { app } from "hyperapp";
const reduxStore = createStore((state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT": return { count: state.count + 1 };
default: return state;
}
});
const state = { count: reduxStore.getState().count };
const actions = {
increment: () => (state, actions) => {
reduxStore.dispatch({ type: "INCREMENT" });
return { count: reduxStore.getState().count };
}
};
const view = (state, actions) =>
h("div", {}, [
h("p", {}, state.count),
h("button", { onclick: actions.increment }, "Increment")
]);
app({ init: state, view, node: document.getElementById("app"), actions });
Здесь Hyperapp выступает как легковесный UI-слой, а Redux управляет глобальным состоянием. Важна синхронизация: состояние Hyperapp обновляется после изменения Redux, что предотвращает рассинхронизацию интерфейса.
Для анимаций популярны библиотеки вроде GSAP или Anime.js. Hyperapp позволяет запускать анимации через хуки жизненного цикла компонентов.
Пример с GSAP:
import { h, app } from "hyperapp";
import gsap from "gsap";
const state = {};
const actions = {};
const view = () =>
h("div", {
id: "box",
style: { width: "100px", height: "100px", background: "red" },
oncreate: (element) => {
gsap.to(element, { x: 300, duration: 2, repeat: -1, yoyo: true });
}
});
app({ init: state, view, node: document.getElementById("app"), actions });
Использование oncreate гарантирует, что элемент уже
находится в DOM, и анимация будет работать корректно.
Hyperapp не имеет собственного набора компонентов, поэтому часто
требуется интеграция с внешними UI-библиотеками, например Material-UI
или Bootstrap. Основная стратегия — использование компонентов как
обычных HTML-элементов и передача данных через свойства
(props).
Пример интеграции с Bootstrap:
const view = (state) =>
h("div", { class: "container" }, [
h("h1", { class: "text-primary" }, "Hyperapp + Bootstrap"),
h("button", { class: "btn btn-success", onclick: () => alert("Clicked!") }, "Click me")
]);
Важно соблюдать концепцию Hyperapp: состояния и действия управляют логикой, а визуальные компоненты остаются декларативными.
Для работы с внешними API Hyperapp предоставляет стандартные
возможности через actions и effects. Внешние
библиотеки вроде Axios или Fetch API можно использовать напрямую.
Пример с Axios:
import axios from "axios";
import { h, app } from "hyperapp";
const state = { users: [] };
const actions = {
fetchUsers: () => async (state, actions) => {
const response = await axios.get("https://jsonplaceholder.typicode.com/users");
return { users: response.data };
}
};
const view = (state, actions) =>
h("div", {}, [
h("button", { onclick: actions.fetchUsers }, "Load Users"),
h("ul", {}, state.users.map(user => h("li", {}, user.name)))
]);
app({ init: state, view, node: document.getElementById("app"), actions });
Асинхронные операции всегда возвращают новое состояние через
actions, что позволяет сохранять декларативную модель
управления.
oncreate,
onupdate и ondestroy для управления сторонними
библиотеками, манипулирующими DOM.