Отслеживание изменений в DOM

Hyperapp использует концепцию виртуального DOM для оптимизации обновлений интерфейса. Вместо прямого взаимодействия с реальным DOM Hyperapp строит виртуальное дерево элементов, которое затем сравнивается с предыдущей версией. Этот процесс называется диффингом. Основное преимущество подхода — минимизация операций с реальным DOM, что повышает производительность приложений.

При создании элемента с помощью функции h формируется виртуальный узел. Каждый узел содержит информацию о теге, атрибутах, событиях и дочерних элементах. Когда вызывается app, Hyperapp строит исходное виртуальное дерево и рендерит его в DOM. Любые изменения состояния приложения инициируют создание нового виртуального дерева и его сравнение с предыдущим.

import { h, app } from "hyperapp";

const state = { count: 0 };

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

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

app({ init: state, view, node: document.getElementById("app"), actions });

В этом примере изменение state.count вызывает перерисовку только того узла, который изменился, без полной переработки всего DOM.

Отслеживание изменений через state

В Hyperapp state является единственным источником правды. Любые изменения DOM должны происходить через изменение состояния. Это гарантирует предсказуемость и консистентность интерфейса.

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

  • Иммутабельность состояния: состояния не изменяются напрямую. Каждое действие возвращает новый объект состояния.
  • Декларативный подход: интерфейс описывается как функция от состояния. Изменение состояния автоматически отражается в виртуальном DOM и, при необходимости, в реальном DOM.
const actions = {
  toggle: () => state => ({ visible: !state.visible })
};

const view = (state, actions) => 
  h("div", {}, [
    h("button", { onclick: actions.toggle }, "Показать/Скрыть"),
    state.visible && h("p", {}, "Этот текст виден только когда visible=true")
  ]);

Использование условного рендеринга позволяет отслеживать появление и исчезновение элементов без прямой работы с DOM.

Эффекты и наблюдение за состоянием

Для работы с асинхронными процессами Hyperapp использует эффекты. Эффекты позволяют выполнять операции вне основной логики состояния, такие как запросы к серверу, таймеры или взаимодействие с локальным хранилищем.

const fetchData = () => (state, actions) => {
  fetch("/data.json")
    .then(res => res.json())
    .then(data => actions.setData(data));
};

const actions = {
  setData: data => state => ({ data }),
  loadData: fetchData
};

Эффекты не изменяют state напрямую. Они вызывают действия, которые создают новый state, инициируя обновление виртуального DOM. Такой подход позволяет точно отслеживать все изменения интерфейса и сохранять его синхронизацию с состоянием приложения.

Подписки на события

Hyperapp предоставляет механизм subscriptions, который позволяет реагировать на внешние события, не связанные напрямую с действиями пользователя. Подписки полезны для обработки событий клавиатуры, ресайза окна или потоков данных в реальном времени.

const subscriptions = state => [
  window.addEventListener("resize", () => console.log(window.innerWidth))
];

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

Контроль обновлений и оптимизация

Hyperapp минимизирует обновления DOM через точечное сравнение узлов:

  • Прямое сравнение по тегу и ключу. Узлы с одинаковым тегом и ключом перерисовываются частично.
  • Использование ключей (key) в списках позволяет оптимизировать ререндеринг динамических коллекций элементов.
  • Избегание побочных эффектов в view. Все побочные операции должны происходить через эффекты или действия, чтобы изменения состояния оставались предсказуемыми.
const view = (state, actions) => 
  h("ul", {}, state.items.map(item =>
    h("li", { key: item.id }, item.name)
  ));

Присвоение ключей гарантирует, что Hyperapp точно идентифицирует элементы списка, предотвращая лишние обновления и сохраняя текущее состояние компонентов.

Жизненный цикл узлов

Hyperapp позволяет использовать hooks для выполнения операций при монтировании, обновлении или удалении элементов:

  • oncreate: вызывается при создании DOM-узла.
  • onupdate: вызывается при обновлении узла после изменения состояния.
  • ondestroy: вызывается перед удалением узла из DOM.
h("div", { 
  oncreate: el => console.log("Создан элемент", el),
  onupdate: el => console.log("Обновлён элемент", el),
  ondestroy: el => console.log("Удалён элемент", el)
}, "Тест")

Эти хуки предоставляют тонкий контроль за жизненным циклом элементов и позволяют отслеживать изменения не только через state, но и на уровне реального DOM.

Вывод

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