Представления (View) и создание пользовательского интерфейса

Hyperapp — минималистичный JavaScript-фреймворк, ориентированный на создание быстрых и декларативных пользовательских интерфейсов. В его основе лежет концепция представления как функции, которая принимает состояние и возвращает описание интерфейса. Такой подход позволяет легко управлять UI, обновляя его при изменении состояния без прямой работы с DOM.


Декларативное представление

В Hyperapp представление описывается с помощью функции view, которая получает текущее состояние приложения и возвращает виртуальный DOM. Синтаксис напоминает JSX, но в чистом Hyperapp используется объектная нотация:

const view = (state) => 
  h("div", { id: "app" }, [
    h("h1", {}, `Привет, ${state.name}`),
    h("button", { onclick: state.changeName }, "Изменить имя")
  ]);

Здесь h — это функция создания виртуальных узлов (hyperscript). Первым аргументом идет тег элемента, вторым — объект атрибутов, третьим — содержимое или массив дочерних узлов.

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

  • Представление чисто декларативное: функция view не изменяет DOM напрямую.
  • Все изменения происходят через обновление состояния.
  • Атрибуты элементов задаются через объект, события — через функции.

Функции представления и их композиция

Hyperapp поддерживает композицию представлений, что позволяет разделять интерфейс на независимые компоненты:

const Header = (props) => 
  h("header", {}, h("h1", {}, props.title));

const Button = (props) =>
  h("button", { onclick: props.onClick }, props.label);

const view = (state) => 
  h("div", {}, [
    Header({ title: "Мое приложение" }),
    Button({ label: "Нажми меня", onClick: state.handleClick })
  ]);

Преимущества такого подхода:

  • Упрощается повторное использование кода.
  • Компоненты остаются чистыми функциями, что облегчает тестирование.
  • Интерфейс можно легко масштабировать без усложнения структуры.

Связывание событий с состоянием

В Hyperapp события привязываются через объект атрибутов, где обработчики вызывают действия (actions). Действие — это функция, которая изменяет состояние:

const state = { count: 0 };

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

const view = (state, actions) =>
  h("div", {}, [
    h("h1", {}, `Счет: ${state.count}`),
    h("button", { onclick: () => actions.increment(1) }, "Увеличить")
  ]);

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

  • События могут принимать параметры через обертку в стрелочную функцию.
  • Изменение состояния происходит только через действия, что делает приложение предсказуемым.
  • Hyperapp автоматически обновляет только те части DOM, которые изменились.

Управление списками и ключами

Для динамических списков Hyperapp поддерживает ключи для элементов. Это важно для корректного обновления интерфейса при добавлении или удалении элементов:

const view = (state, actions) =>
  h("ul", {}, state.items.map(item =>
    h("li", { key: item.id }, item.text)
  ));

Значение ключей:

  • Ключи позволяют Hyperapp оптимизировать перерисовку.
  • Элементы с одинаковыми ключами сохраняют состояние между обновлениями.
  • Без ключей при изменении списка может происходить излишняя перерисовка.

Интерактивные элементы и формы

Hyperapp поддерживает работу с формами через события oninput и onsubmit. Состояние формы привязывается к свойствам состояния:

const state = { text: "" };

const actions = {
  updateText: (value) => () => ({ text: value }),
  submit: () => (state) => { console.log(state.text); return state; }
};

const view = (state, actions) =>
  h("form", { onsubmit: (e) => { e.preventDefault(); actions.submit(); } }, [
    h("input", { type: "text", value: state.text, oninput: (e) => actions.updateText(e.target.value) }),
    h("button", { type: "submit" }, "Отправить")
  ]);

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

  • Данные формы полностью управляются через состояние.
  • Любое изменение input автоматически отражается в состоянии.
  • Обработка отправки формы через событие onsubmit позволяет предотвратить стандартное поведение браузера.

Декларативные стили и классы

Стили и классы задаются через объект атрибутов:

h("div", { class: "container", style: { color: "red", fontSize: "20px" } }, "Пример")

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

  • class принимает строку с именами классов.
  • style — объект, где ключи соответствуют свойствам CSS в camelCase.
  • Состояние может динамически менять классы и стили:
h("div", { class: state.active ? "active" : "inactive" }, "Статус")

Работа с вложенными компонентами

Hyperapp позволяет строить сложные интерфейсы с глубокой вложенностью компонентов без потери производительности:

const TodoItem = ({ item, toggle }) =>
  h("li", { onclick: () => toggle(item.id) }, item.text);

const TodoList = ({ items, toggle }) =>
  h("ul", {}, items.map(item => TodoItem({ item, toggle })));

Принципы:

  • Каждый компонент получает только необходимые данные.
  • Изменение состояния одного компонента не затрагивает другие.
  • Интерфейс остается модульным и легко расширяемым.

Итоговая структура Hyperapp-приложения

Полноценное приложение строится по принципу:

  1. Состояние (state) — хранит данные интерфейса.
  2. Действия (actions) — функции, изменяющие состояние.
  3. Представление (view) — функция, которая описывает интерфейс на основе состояния.
  4. Композиция компонентов — разбиение UI на небольшие, переиспользуемые функции.

Такой подход обеспечивает предсказуемость, простоту и масштабируемость интерфейсов, позволяя создавать интерактивные приложения с минимальными усилиями.