Создание и композиция компонентов

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

Минимальный компонент представляет собой функцию, возвращающую vnode:

const Title = (props) =>
  <h1>{props.text}</h1>

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


Передача данных через свойства

Композиция компонентов в Hyperapp основана на передаче свойств (props). Любые данные, необходимые для отображения, передаются в виде аргумента функции-компонента.

const UserCard = ({ name, age }) =>
  <div class="user-card">
    <span class="name">{name}</span>
    <span class="age">{age}</span>
  </div>

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

const App = () =>
  <main>
    <UserCard name="Ирина" age={29} />
    <UserCard name="Алексей" age={34} />
  </main>

Компоненты остаются полностью детерминированными: одинаковые входные данные всегда дают одинаковый результат.


Компоненты и глобальное состояние

Состояние приложения в Hyperapp хранится централизованно. Компоненты получают доступ к нему через аргументы state и actions, передаваемые корневому представлению.

const state = {
  count: 0
}

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

Компонент, зависящий от состояния, описывается как функция от state и actions:

const Counter = (state, actions) =>
  <div>
    <span>{state.count}</span>
    <button oncl ick={actions.increment}>+</button>
  </div>

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


Частичные представления и декомпозиция

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

const CounterValue = ({ value }) =>
  <span class="value">{value}</span>

const CounterButton = ({ onClick }) =>
  <button oncl ick={onClick}>+</button>

const Counter = (state, actions) =>
  <div>
    <CounterValue value={state.count} />
    <CounterButton onCl ick={actions.increment} />
  </div>

Декомпозиция снижает связанность и делает компоненты повторно используемыми. Каждый подкомпонент можно тестировать изолированно, подставляя фиктивные данные.


Компоненты высшего порядка

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

const withBorder = (Component) => (props) =>
  <div class="border">
    <Component {...props} />
  </div>

Применение обёртки:

const BorderedTitle = withBorder(Title)

const App = () =>
  <BorderedTitle text="Заголовок" />

Композиция через функции не требует специальных API и остаётся частью обычного JavaScript.


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

Условное отображение элементов реализуется стандартными выражениями языка. Компоненты могут возвращать разные поддеревья в зависимости от входных данных.

const Status = ({ online }) =>
  online
    ? <span class="online">Онлайн</span>
    : <span class="offline">Оффлайн</span>

Для более сложных случаев используется логическое И:

const Notification = ({ message }) =>
  <div>
    {message && <p>{message}</p>}
  </div>

Такой стиль не вводит дополнительных абстракций и остаётся прозрачным.


Списки и композиция однотипных компонентов

Рендеринг списков строится на стандартном методе map, возвращающем массив vnode-элементов.

const TodoItem = ({ text, done }) =>
  <li class={done ? "done" : ""}>{text}</li>

const TodoList = ({ items }) =>
  <ul>
    {items.map(item =>
      <TodoItem text={item.text} done={item.done} />
    )}
  </ul>

Каждый элемент списка является полноценным компонентом, что упрощает расширение логики и изменение внешнего вида.


Изоляция логики и чистота компонентов

Компоненты в Hyperapp рекомендуется держать максимально простыми. Вся бизнес-логика выносится в actions или отдельные функции, не связанные с представлением.

const formatName = (user) =>
  `${user.lastName} ${user.firstName}`

const UserName = ({ user }) =>
  <span>{formatName(user)}</span>

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


Иерархия компонентов и контроль зависимостей

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

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


Повторное использование и параметризация

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

const Button = ({ label, onClick, type }) =>
  <button class={type} oncl ick={onClick}>
    {label}
  </button>

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