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

Mithril — это современный JavaScript-фреймворк для построения одностраничных приложений (SPA), отличающийся минималистичностью и высокой производительностью. Компонентная архитектура является ключевым принципом работы с Mithril и позволяет создавать приложения, структурированные в виде иерархии независимых, повторно используемых компонентов.


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

Компонент в Mithril представляет собой объект с как минимум одним методом view. Этот метод возвращает описание интерфейса в формате виртуального DOM. Простейший компонент выглядит следующим образом:

const Hello = {
    view: () => m("div", "Привет, Mithril!")
};
  • view — единственный обязательный метод компонента.
  • m() — функция создания виртуальных узлов (vnode), принимающая тег, атрибуты и дочерние элементы.

Компоненты могут быть функциональными или объектными:

// Функциональный компонент
const Functional = () => m("span", "Функциональный компонент");

// Объектный компонент с состоянием
const Stateful = {
    count: 0,
    view: () => m("button", { onclick: () => Stateful.count++ }, Stateful.count)
};

Функциональные компоненты подходят для статических или простых динамических интерфейсов. Объектные компоненты удобны, когда требуется хранение внутреннего состояния.


Встроенное состояние и жизненный цикл

Mithril предоставляет механизмы управления состоянием и жизненным циклом компонентов через специальные методы:

  • oninit(vnode) — вызывается один раз при инициализации компонента.
  • oncreate(vnode) — вызывается после монтирования элемента в DOM.
  • onupdate(vnode) — вызывается после обновления DOM.
  • onremove(vnode) — вызывается перед удалением компонента.
  • onbeforeremove(vnode) — позволяет добавить асинхронное поведение перед удалением.

Пример использования жизненного цикла:

const Timer = {
    seconds: 0,
    oninit: () => Timer.interval = setInterval(() => {
        Timer.seconds++;
        m.redraw();
    }, 1000),
    onremove: () => clearInterval(Timer.interval),
    view: () => m("div", `Прошло секунд: ${Timer.seconds}`)
};
  • m.redraw() — инициирует повторный рендер компонента.
  • Управление таймерами и сторонними ресурсами через жизненный цикл предотвращает утечки памяти.

Передача данных через атрибуты

Компоненты Mithril могут принимать параметры через объект attrs, передаваемый при создании vnodes:

const Greeting = {
    view: ({ attrs }) => m("h1", `Привет, ${attrs.name}!`)
};

m.mount(document.body, {
    view: () => m(Greeting, { name: "Мир" })
});
  • attrs содержит все переданные свойства.
  • Используется для динамической настройки компонента и передачи данных между компонентами.

Вложенные и составные компоненты

Компоненты могут быть вложенными, что позволяет строить сложные интерфейсы из небольших, переиспользуемых частей:

const Header = { view: () => m("header", "Заголовок") };
const Content = { view: () => m("main", "Основное содержимое") };
const Footer = { view: () => m("footer", "Подвал") };

const Page = {
    view: () => m("div", [ m(Header), m(Content), m(Footer) ])
};

m.mount(document.body, Page);
  • Вложение компонентов реализуется через массивы или прямое добавление vnodes.
  • Поддерживается композиция и делегирование функционала, что снижает связность кода.

Управление состоянием и реактивность

Mithril использует простой, но мощный механизм реактивного обновления интерфейса:

  • Любое изменение состояния должно сопровождаться вызовом m.redraw().
  • Для локального состояния часто применяются функции-замыкания или свойства компонента.
  • Для глобального состояния удобно использовать отдельные модули с объектами-синглтонами.

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

const Counter = {
    count: 0,
    view: () => m("div", [
        m("span", `Счетчик: ${Counter.count}`),
        m("button", { onclick: () => { Counter.count++; m.redraw(); } }, "Увеличить")
    ])
};
  • В данном случае состояние хранится внутри объекта компонента.
  • Механизм m.redraw() гарантирует синхронное обновление интерфейса.

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

Mithril не навязывает способ стилизации, но рекомендуются следующие подходы:

  1. Инлайновые стили через объект атрибутов:
m("div", { style: { color: "blue", fontWeight: "bold" } }, "Текст")
  1. CSS-классы через атрибут class или className:
m("div", { class: "my-class another-class" }, "Текст")
  1. Модульные CSS или CSS-in-JS для крупных приложений, чтобы избежать глобальных конфликтов.

Асинхронные компоненты

Компоненты могут загружать данные асинхронно с помощью oninit и промисов. Mithril автоматически вызывает m.redraw() при завершении промиса через m.request.

const Users = {
    users: [],
    oninit: () => {
        m.request({ method: "GET", url: "/api/users" })
            .then(data => Users.users = data);
    },
    view: () => m("ul", Users.users.map(u => m("li", u.name)))
};
  • Асинхронная загрузка данных интегрируется с жизненным циклом.
  • Компонент обновляется только после изменения состояния, что минимизирует лишние рендеры.

Принципы построения компонентной архитектуры

  1. Разделение ответственности: каждый компонент решает одну конкретную задачу.
  2. Повторное использование: маленькие компоненты объединяются в составные.
  3. Ясная структура данных: состояние и атрибуты отделяются, данные передаются сверху вниз.
  4. Минимальная зависимость: компоненты взаимодействуют через attrs и события, избегая глобальных переменных.
  5. Оптимизация рендеринга: избегать лишних вызовов m.redraw() и тяжелых вычислений в view.

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