Разделение ответственности

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

Компоненты и их структура

Компонент в Mithril — это объект с методами view и, при необходимости, oninit, oncreate, onupdate, onremove.

Пример базового компонента:

const Counter = {
    count: 0,
    view: function() {
        return m("div", [
            m("button", { onclick: () => this.count-- }, "-"),
            m("span", this.count),
            m("button", { onclick: () => this.count++ }, "+")
        ]);
    }
};

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

  • view отвечает исключительно за рендеринг и не содержит сложной логики изменения данных вне компонента.
  • oninit используется для инициализации состояния компонента.
  • Взаимодействие с внешними сервисами или API лучше вынести в отдельные функции или модули, сохраняя компонент «чистым».

Разделение состояния и представления

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

Пример передачи состояния через props:

const Display = {
    view: ({ attrs }) => m("p", `Счёт: ${attrs.value}`)
};

const App = {
    count: 0,
    view: function() {
        return m("div", [
            m("button", { onclick: () => this.count-- }, "-"),
            m(Display, { value: this.count }),
            m("button", { onclick: () => this.count++ }, "+")
        ]);
    }
};

Такой подход обеспечивает:

  • Повторное использование компонентов с различными данными.
  • Легкость тестирования и отладки, так как Display не зависит от внутреннего состояния App.

Модульность и сервисы

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

Пример сервиса:

const TodoService = {
    todos: [],
    fetchTodos: async function() {
        const response = await fetch("/api/todos");
        this.todos = await response.json();
    },
    addTodo: function(todo) {
        this.todos.push(todo);
    }
};

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

const TodoList = {
    oninit: () => TodoService.fetchTodos(),
    view: () => m("ul", TodoService.todos.map(todo => m("li", todo.text)))
};

Выделение сервисов позволяет:

  • Сохранять компонент легким и ориентированным на UI.
  • Централизовать работу с данными, упрощая обновление и масштабирование приложения.

События и взаимодействие компонентов

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

Пример:

const Button = {
    view: ({ attrs }) => m("button", { onclick: attrs.onClick }, attrs.label)
};

const Counter = {
    count: 0,
    view: function() {
        return m("div", [
            m(Button, { label: "-", onClick: () => this.count-- }),
            m("span", this.count),
            m(Button, { label: "+", onClick: () => this.count++ })
        ]);
    }
};

Преимущества такого подхода:

  • Четкое разделение UI и логики изменения состояния.
  • Возможность легко менять поведение компонентов без изменения их внутренней структуры.

Итоги подхода

Разделение ответственности в Mithril строится на трех уровнях:

  1. Компоненты отвечают только за представление и локальное состояние.
  2. Сервисы и модули управляют данными и бизнес-логикой.
  3. Props и события обеспечивают взаимодействие между компонентами без прямого связывания.

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