Паттерн "Контейнер-Презентатор"

Паттерн «Контейнер-Презентатор» (Container-Presenter) позволяет разделять логику управления состоянием и визуальное представление компонентов. В контексте Hyperapp это особенно актуально, так как фреймворк по своей природе минималистичен и концентрируется на чистой функции view и одном глобальном state.


Контейнерные компоненты

Контейнерный компонент отвечает за:

  • Управление состоянием.
  • Подключение к внешним источникам данных.
  • Обработку бизнес-логики.
  • Формирование пропсов для презентатора.

Контейнерные компоненты не содержат JSX-разметки и элементов UI, их задача — подготовить данные и действия для дочерних компонентов.

Пример контейнера в Hyperapp:

import { h } from "hyperapp";
import TodoList from "./TodoList";

const TodoContainer = ({ state, actions }) => {
  const addTodo = (text) => {
    actions.addTodo({ id: Date.now(), text });
  };

  const removeTodo = (id) => {
    actions.removeTodo(id);
  };

  return h(TodoList, {
    todos: state.todos,
    addTodo,
    removeTodo
  });
};

export default TodoContainer;

Здесь контейнер передаёт данные и функции управления состоянием в компонент-презентатор TodoList.


Презентаторные компоненты

Презентаторные компоненты сосредоточены исключительно на отображении интерфейса. Они:

  • Получают props от контейнера.
  • Не знают о глобальном состоянии.
  • Не выполняют побочных эффектов (API-запросы, маршрутизация и т.д.).

Пример презентатора:

import { h } from "hyperapp";

const TodoList = ({ todos, addTodo, removeTodo }) => {
  let input;

  return h("div", {}, [
    h("ul", {}, todos.map(todo =>
      h("li", {}, [
        todo.text,
        h("button", { onclick: () => removeTodo(todo.id) }, "Удалить")
      ])
    )),
    h("input", { oninput: e => input = e.target.value }),
    h("button", { onclick: () => addTodo(input) }, "Добавить")
  ]);
};

export default TodoList;

Презентатор полностью отделён от логики приложения: он только отображает список задач и вызывает переданные функции.


Связь контейнера и презентатора

Главная цель паттерна — минимизировать зависимость UI от состояния приложения. Контейнер управляет state и действиями, а презентатор — отрисовкой. Это обеспечивает:

  • Упрощённое тестирование: презентатор тестируется как чистая функция.
  • Повторное использование компонентов.
  • Чёткое разграничение ответственности.

Использование с Hyperapp actions

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

const actions = {
  addTodo: (todo) => (state) => ({ todos: [...state.todos, todo] }),
  removeTodo: (id) => (state) => ({ todos: state.todos.filter(t => t.id !== id) })
};

const state = { todos: [] };

Контейнер получает state и actions и формирует пропсы для презентатора. Презентатор остаётся чистым UI.


Комбинирование нескольких презентаторов

Контейнер может объединять несколько презентаторов, создавая сложные интерфейсы из маленьких компонентов:

const AppContainer = ({ state, actions }) => {
  return h("div", {}, [
    h(Header, { title: "Список задач" }),
    h(TodoContainer, { state, actions }),
    h(Footer, {})
  ]);
};

Такой подход сохраняет чистоту и модульность интерфейса.


Поддержка асинхронных операций

Контейнеры удобны для работы с асинхронными запросами:

const TodoContainer = ({ state, actions }) => {
  const fetchTodos = async () => {
    const response = await fetch("/api/todos");
    const data = await response.json();
    actions.setTodos(data);
  };

  return h(TodoList, {
    todos: state.todos,
    fetchTodos
  });
};

Презентатор не знает о fetch, он просто вызывает переданную функцию.


Преимущества паттерна в Hyperapp

  • Чёткое разделение обязанностей между логикой и отображением.
  • Повторное использование UI-компонентов без зависимости от состояния.
  • Упрощённое тестирование и отладка.
  • Снижение связности кода, что критично для крупных приложений.
  • Поддержка асинхронных действий без изменения UI-компонентов.

Паттерн «Контейнер-Презентатор» в Hyperapp превращает минималистичный фреймворк в гибкий инструмент для построения сложных, масштабируемых приложений, где логика и визуальное представление остаются независимыми и легко тестируемыми.