Вложенная маршрутизация

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


Основы маршрутизации

В Mithril маршруты настраиваются с помощью объекта m.route. Основной синтаксис:

m.route(root, defaultRoute, {
    "/home": HomeComponent,
    "/about": AboutComponent
});
  • root — корневой DOM-элемент приложения.
  • defaultRoute — маршрут, на который будет перенаправлен пользователь при загрузке.
  • Объект маршрутов связывает URL с компонентами.

Каждый компонент маршрута должен реализовывать либо метод view, либо возвращать функцию, генерирующую виртуальный DOM.


Концепция вложенных маршрутов

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

Пример структуры:

/dashboard
/dashboard/profile
/dashboard/settings

Здесь /dashboard является родительским маршрутом, а /dashboard/profile и /dashboard/settings — дочерними. Родительский компонент управляет общим интерфейсом, например, боковой панелью, а дочерние компоненты подставляются в область контента.


Реализация вложенной маршрутизации

Для вложенной маршрутизации создаётся отдельный внутренний m.route в компоненте родителя. Пример:

const Dashboard = {
    view: () => m("div", [
        m("nav", [
            m("a[href='/dashboard/profile']", { oncreate: m.route.link }, "Profile"),
            m("a[href='/dashboard/settings']", { oncreate: m.route.link }, "Settings")
        ]),
        m("div#dashboard-content") // контейнер для дочерних маршрутов
    ]),
    oncreate: () => {
        m.route(document.getElementById("dashboard-content"), "/dashboard/profile", {
            "/dashboard/profile": Profile,
            "/dashboard/settings": Settings
        });
    }
};

Особенности:

  • Вложенный m.route инициализируется в методе oncreate родительского компонента.
  • Контейнер дочерних маршрутов (div#dashboard-content) становится корнем для внутренней маршрутизации.
  • Использование m.route.link гарантирует корректное обновление URL без перезагрузки страницы.

Динамические параметры и вложенные маршруты

Вложенные маршруты могут использовать динамические параметры, например идентификаторы:

m.route(document.getElementById("dashboard-content"), "/dashboard/user/1", {
    "/dashboard/user/:id": UserDetail
});

Компонент UserDetail получает параметр id через объект vnode.attrs:

const UserDetail = {
    view: (vnode) => m("div", `Пользователь ID: ${vnode.attrs.id}`)
};

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


Управление переходами между уровнями

Для перехода между родительскими и дочерними маршрутами используется стандартная функция m.route.set:

m.route.set("/dashboard/settings");

При этом состояние родительского компонента сохраняется, а дочерний компонент обновляется в пределах контейнера.


Организация кода при больших приложениях

Вложенная маршрутизация упрощает модульное построение приложения:

  • Родительский компонент отвечает за шаблон страницы и навигацию.
  • Дочерние маршруты — за конкретный функционал (список, профиль, настройки).
  • Возможность динамической подгрузки компонентов через import() позволяет оптимизировать размер бандла.

Пример с ленивой загрузкой:

m.route(document.getElementById("dashboard-content"), "/dashboard/profile", {
    "/dashboard/profile": () => import("./Profile.js"),
    "/dashboard/settings": () => import("./Settings.js")
});

Особенности и подводные камни

  1. Контейнер для вложенного маршрута должен существовать до инициализации вложенной маршрутизации. Иначе m.route не сможет привязать дочерние маршруты.
  2. Состояние родительского компонента не сбрасывается при переходе между дочерними маршрутами, если вложенная маршрутизация правильно настроена.
  3. Использование одинакового корневого элемента для нескольких вложенных маршрутов может вызвать конфликты. Каждый уровень лучше изолировать отдельным DOM-узлом.

Пример полной структуры

const App = {
    view: () => m("div", m("div#app-content"))
};

const Dashboard = {
    view: () => m("div", [
        m("h2", "Панель управления"),
        m("nav", [
            m("a[href='/dashboard/profile']", { oncreate: m.route.link }, "Profile"),
            m("a[href='/dashboard/settings']", { oncreate: m.route.link }, "Settings")
        ]),
        m("div#dashboard-content")
    ]),
    oncreate: () => {
        m.route(document.getElementById("dashboard-content"), "/dashboard/profile", {
            "/dashboard/profile": Profile,
            "/dashboard/settings": Settings
        });
    }
};

m.route(document.getElementById("app-content"), "/dashboard", {
    "/dashboard": Dashboard
});

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


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