Условный рендеринг и работа со списками в Hyperapp опираются на фундаментальные принципы фреймворка: чистые функции представления, неизменяемое состояние и декларативное описание интерфейса. Все решения строятся вокруг обычного JavaScript без дополнительного DSL, что делает поведение предсказуемым и прозрачным.
В Hyperapp нет специальных директив или шаблонных конструкций для
условий. Условный рендеринг реализуется с помощью стандартных выражений
JavaScript внутри функции view.
Функция view возвращает виртуальное дерево. Это
означает, что любые условия определяют, какие узлы будут
включены в это дерево.
Простейший пример:
const view = state =>
h("div", {}, [
state.loggedIn
? h("p", {}, "Пользователь авторизован")
: h("p", {}, "Гость")
])
Здесь используется тернарный оператор. В зависимости от значения
state.loggedIn в виртуальное дерево попадает один из двух
элементов.
Ключевой момент: рендеринг — это просто вычисление значения функции, а не пошаговое обновление DOM.
Часто применяется оператор && для отображения
элемента только при истинном условии:
const view = state =>
h("div", {}, [
state.error && h("div", { class: "error" }, state.error)
])
Если state.error равно null,
false или undefined, элемент не будет добавлен
в виртуальное дерево.
Особенность Hyperapp:
false, null и undefined
игнорируются рендерером0 и пустая строка будут отрисованыКогда требуется условно отрисовать несколько узлов, используется массив или фрагмент:
const view = state =>
h("div", {}, [
state.loading && [
h("span", {}, "Загрузка"),
h("span", { class: "spinner" })
]
])
Hyperapp корректно обрабатывает вложенные массивы и автоматически «расплющивает» их при построении виртуального DOM.
Для сложных условий целесообразно выносить рендеринг в отдельные функции:
const renderStatus = state => {
if (state.loading) {
return h("p", {}, "Загрузка данных")
}
if (state.error) {
return h("p", { class: "error" }, state.error)
}
return h("p", {}, "Готово")
}
const view = state =>
h("div", {}, [
renderStatus(state)
])
Такой подход:
Списки в Hyperapp — это отображение массивов состояния в массивы виртуальных узлов.
Базовый пример:
const view = state =>
h("ul", {},
state.items.map(item =>
h("li", {}, item)
)
)
Каждый элемент массива преобразуется в виртуальный DOM-узел.
Принципиально важно: никакого скрытого механизма итерации не
существует, используется обычный Array.map.
Для корректного диффинга и минимальных обновлений DOM необходимо указывать ключи.
В Hyperapp ключ задаётся через свойство key:
const view = state =>
h("ul", {},
state.items.map(item =>
h("li", { key: item.id }, item.text)
)
)
Роль ключа:
Ключ должен быть:
Элементы списка могут содержать вложенные компоненты и условия:
const view = state =>
h("ul", {},
state.todos.map(todo =>
h("li", { key: todo.id }, [
h("span", { class: todo.done ? "done" : "" }, todo.title),
!todo.done && h("button", { onclick: toggle(todo.id) }, "Завершить")
])
)
)
Здесь совмещаются:
Hyperapp корректно обрабатывает все комбинации, так как итогом всегда остаётся чистое виртуальное дерево.
Часто требуется отобразить сообщение при отсутствии элементов:
const view = state =>
h("div", {}, [
state.items.length === 0
? h("p", {}, "Список пуст")
: h("ul", {},
state.items.map(item =>
h("li", { key: item.id }, item.name)
)
)
])
Альтернативный вариант — предварительная проверка:
const view = state => {
if (state.items.length === 0) {
return h("p", {}, "Список пуст")
}
return h("ul", {},
state.items.map(item =>
h("li", { key: item.id }, item.name)
)
)
}
Оба подхода эквивалентны с точки зрения рендеринга.
Часто списки отображаются не напрямую, а через производные данные:
const view = state =>
h("ul", {},
state.items
.filter(item => item.visible)
.sort((a, b) => a.order - b.order)
.map(item =>
h("li", { key: item.id }, item.title)
)
)
Следует учитывать:
Hyperapp не накладывает ограничений на глубину вложенности:
const view = state =>
h("div", {},
state.categories.map(category =>
h("section", { key: category.id }, [
h("h2", {}, category.name),
h("ul", {},
category.items.map(item =>
h("li", { key: item.id }, item.title)
)
)
])
)
)
Каждый уровень списка работает по тем же правилам:
mapИногда требуется не фильтровать массив, а условно исключать элементы
прямо в map:
state.items.map(item =>
item.hidden
? null
: h("li", { key: item.id }, item.text)
)
null будет проигнорирован рендерером и не попадёт в
DOM.
Условный рендеринг в Hyperapp всегда основан на состоянии. Это означает:
Изменение состояния автоматически приводит к перерасчёту условий и списков, а диффер виртуального DOM обновляет только необходимые части реального DOM.
Использование индекса массива как ключа Приводит к ошибкам при изменении порядка элементов.
Мутация массива при рендеринге Методы вроде
sort без копирования могут нарушить неизменяемость
состояния.
Сложная логика прямо в JSX-подобном коде Ухудшает читаемость и сопровождение.
map, без специальных APInull, false, undefined не
попадают в DOMЭти правила формируют предсказуемую и масштабируемую модель отображения интерфейса в Hyperapp.