Условный рендеринг и списки

Условный рендеринг и работа со списками в 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)
    )
  )

Роль ключа:

  • позволяет сохранить связь между виртуальным и реальным DOM
  • предотвращает некорректное переиспользование элементов
  • особенно важно при сортировке, удалении и вставке

Ключ должен быть:

  • стабильным
  • уникальным в пределах списка
  • примитивным значением (строка или число)

Списки со сложной структурой

Элементы списка могут содержать вложенные компоненты и условия:

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
  • нет необходимости скрывать элементы через CSS для логики
  • интерфейс всегда является функцией состояния

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


Типичные ошибки

Использование индекса массива как ключа Приводит к ошибкам при изменении порядка элементов.

Мутация массива при рендеринге Методы вроде sort без копирования могут нарушить неизменяемость состояния.

Сложная логика прямо в JSX-подобном коде Ухудшает читаемость и сопровождение.


Итоговые принципы

  • Условный рендеринг — это обычные выражения JavaScript
  • Списки строятся через map, без специальных API
  • Ключи обязательны для динамических коллекций
  • null, false, undefined не попадают в DOM
  • Представление остаётся чистой функцией состояния

Эти правила формируют предсказуемую и масштабируемую модель отображения интерфейса в Hyperapp.