route(root, defaultRoute, routes)
Описание
Навигация между "страницами" в приложении.
var Home = {
view: function () {
return 'Welcome';
},
};
m.route(document.body, '/home', {
'/home': Home, // определяет `https://localhost/#!/home`
});
В приложении может быть только один вызов функции m.route
.
Сигнатура
m.route(root, defaultRoute, routes)
Аргумент | Тип | Обязательный | Описание |
---|---|---|---|
root | Element | Да | DOM-элемент, который будет родительским узлом для поддерева. |
defaultRoute | String | Да | Маршрут, на который следует перенаправить, если текущий URL не соответствует ни одному из определенных маршрутов. Обратите внимание, что это не начальный маршрут; начальным маршрутом будет URL в адресной строке. |
routes | Object<String,Component|RouteResolver> | Да | Объект, где ключи являются строками маршрутов, а значения - компонентами или RouteResolver. |
возвращает | undefined |
Статические члены
m.route.set
Перенаправляет на указанный маршрут или на маршрут по умолчанию, если соответствующий маршрут не найден. Запускает асинхронную перерисовку всех точек монтирования.
m.route.set(path, params, options)
Аргумент | Тип | Обязательный | Описание |
---|---|---|---|
path | String | Да | Имя пути для маршрутизации, без префикса. Путь может включать параметры, значения которых интерполируются из объекта params . |
params | Object | Нет | Параметры маршрутизации. Если path содержит слоты для параметров маршрутизации, свойства этого объекта интерполируются в строку пути. |
options.replace | Boolean | Нет | Указывает, следует ли создавать новую запись в истории браузера или заменять текущую. По умолчанию false . |
options.state | Object | Нет | Объект state , который передается в базовый вызов history.pushState / history.replaceState . Этот объект состояния становится доступным в свойстве history.state и добавляется в объект параметров маршрутизации. Обратите внимание, что этот параметр работает только при использовании API pushState , но игнорируется, если маршрутизатор возвращается в режим hashchange (т.е. если API pushState недоступен). |
options.title | String | Нет | Строка title , которая передается в базовый вызов history.pushState / history.replaceState . |
возвращает | undefined |
Помните, что при использовании .set
с params
необходимо также определить соответствующий маршрут:
var Article = {
view: function (vnode) {
return 'This is article ' + vnode.attrs.articleid;
},
};
m.route(document.body, {
'/article/:articleid': Article,
});
m.route.set('/article/:articleid', { articleid: 1 });
m.route.get
Возвращает последний полностью разрешенный путь маршрутизации без префикса. Он может отличаться от пути, отображаемого в адресной строке, пока асинхронный маршрут находится в ожидании разрешения.
path = m.route.get()
Аргумент | Тип | Обязательный | Описание |
---|---|---|---|
возвращает | String | Последний полностью разрешенный путь. |
m.route.prefix
Определяет префикс маршрутизатора. Префикс маршрутизатора - это фрагмент URL-адреса, который определяет базовую стратегию, используемую маршрутизатором.
m.route.prefix = prefix
Аргумент | Тип | Обязательный | Описание |
---|---|---|---|
prefix | String | Да | Префикс, который управляет базовой стратегией маршрутизации, используемой Mithril. |
Это обычное свойство, поэтому его можно как читать, так и изменять.
m.route.Link
Этот компонент создает динамическую ссылку для маршрутизации. Его основная функция - создавать a
ссылки с локальными href
, преобразованными с учетом префикса маршрута.
m(m.route.Link, { href: '/foo' }, 'foo');
// Если m.route.prefix не изменился по сравнению со стратегией по умолчанию, отображается:
// <a href="#!/foo">foo</a>
Ссылки принимают набор специальных атрибутов:
selector
- это то, что будет передано в качестве первого аргумента вm
: любой селектор является допустимым, включая элементы, отличные отa
.params
&options
- это аргументы с теми же именами, что и вm.route.set
.disabled
, еслиtrue
, отключает поведение маршрутизации и любой связанный обработчикonclick
и добавляет атрибутdata-disabled="true"
для указания состояния недоступности; если элемент являетсяa
, атрибутhref
удаляется.
Поведение маршрутизации нельзя предотвратить с помощью API обработки событий: используйте disabled
вместо этого.
m(
m.route.Link,
{
href: '/foo',
selector: 'button.large',
disabled: true,
params: { key: 'value' },
options: { replace: true },
},
'link name'
);
// Отображается:
// <button disabled aria-disabled="true" class="large">link name</button>
vnode = m(m.route.Link, attributes, children)
Аргумент | Тип | Обязательный | Описание |
---|---|---|---|
attributes.href | Object | Да | Целевой маршрут для перехода. |
attributes.disabled | Boolean | Нет | Отключает элемент. |
attributes.selector | String|Object|Function | Нет | Селектор для m , по умолчанию "a" . |
attributes.options | Object | Нет | Устанавливает options , переданные в m.route.set . |
attributes.params | Object | Нет | Устанавливает params , переданные в m.route.set . |
attributes | Object | Нет | Любые другие атрибуты, которые будут переданы в m . |
children | Array<Vnode>|String|Number|Boolean | Нет | Дочерние vnodes для этой ссылки. |
возвращает | Vnode | Vnode. |
m.route.param
Извлекает параметр маршрута из последнего полностью разрешенного маршрута. Параметр маршрута - это пара "ключ-значение". Параметры маршрута могут поступать из нескольких разных источников:
- Интерполяция маршрута (например, если маршрут
/users/:id
и он разрешается в/users/1
, параметр маршрута имеет ключid
и значение"1"
). - Строка запроса маршрутизатора (например, если путь
/users?page=1
, параметр маршрута имеет ключpage
и значение"1"
). history.state
(например, еслиhistory.state
-{foo: "bar"}
, параметр маршрута имеет ключfoo
и значение"bar"
).
value = m.route.param(key)
Аргумент | Тип | Обязательный | Описание |
---|---|---|---|
key | String | Нет | Имя параметра маршрута (например, id в маршруте /users/:id или page в пути /users/1?page=3 или ключ в history.state ). |
возвращает | String|Object | Значение для указанного ключа. Если ключ не указан, возвращает объект, содержащий все ключи интерполяции. |
Обратите внимание, что в функции onmatch
RouteResolver новый маршрут еще не был полностью разрешен, и m.route.param()
вернет параметры предыдущего маршрута, если таковые имеются. onmatch
получает параметры нового маршрута в качестве аргумента.
m.route.SKIP
Специальное значение, которое может быть возвращено из onmatch
RouteResolver для перехода к следующему маршруту.
RouteResolver
RouteResolver
- это объект, не являющийся компонентом, который содержит метод onmatch
и/или метод render
. Оба метода являются необязательными, но должен присутствовать хотя бы один.
Если объект может быть обнаружен как компонент (по наличию метода view
или по тому, что он является function
/class
), он будет рассматриваться как таковой, даже если у него есть методы onmatch
или render
. Поскольку RouteResolver
не является компонентом, у него нет методов жизненного цикла.
Как правило, RouteResolver
должны находиться в том же файле, что и вызов m.route
, тогда как определения компонентов должны находиться в своих собственных модулях.
routeResolver = {onmatch, render}
При использовании компонентов вы можете думать о них как о синтаксическом сахаре для этого RouteResolver
, предполагая, что ваш компонент - Home
:
var routeResolver = {
onmatch: function () {
return Home;
},
render: function (vnode) {
return [vnode];
},
};
routeResolver.onmatch
Хук onmatch
вызывается, когда маршрутизатору необходимо найти компонент для рендеринга. Он вызывается один раз для каждого изменения пути маршрутизатора, но не при последующих перерисовках на том же пути. Его можно использовать для запуска логики до инициализации компонента (например, логики аутентификации, предварительной загрузки данных, отслеживания аналитики перенаправления и т. д.).
Этот метод также позволяет определить отображаемый компонент асинхронно, что делает его подходящим для разделения кода и асинхронной загрузки модулей. Чтобы отобразить компонент асинхронно, верните Promise
, который разрешается в компонент.
Для получения дополнительной информации об onmatch
см. раздел расширенное разрешение компонентов.
routeResolver.onmatch(args, requestedPath, route)
Аргумент | Тип | Описание |
---|---|---|
args | Object | Параметры маршрутизации. |
requestedPath | String | Путь маршрутизатора, запрошенный последним действием маршрутизации, включая интерполированные значения параметров маршрутизации, но без префикса. Когда вызывается onmatch , разрешение для этого пути не завершено, и m.route.get() по-прежнему возвращает предыдущий путь. |
route | String | Путь маршрутизатора, запрошенный последним действием маршрутизации, исключая интерполированные значения параметров маршрутизации. |
возвращает | Component|\Promise<Component>|undefined | Компонент или Promise , который разрешается в компонент. |
Если onmatch
возвращает компонент или Promise
, который разрешается в компонент, этот компонент используется в качестве vnode.tag
для первого аргумента в методе render
RouteResolver
. В противном случае vnode.tag
устанавливается в "div"
. Аналогично, если метод onmatch
опущен, vnode.tag
также имеет значение "div"
.
Если onmatch
возвращает Promise
, который отклоняется, маршрутизатор перенаправляет обратно на defaultRoute
. Вы можете переопределить это поведение, вызвав .catch
в цепочке Promise
перед его возвратом.
routeResolver.render
Метод render
вызывается при каждой перерисовке для соответствующего маршрута. Он похож на метод view
в компонентах и существует для упрощения композиции компонентов. Он также позволяет избежать обычного поведения Mithril.js по замене всего поддерева.
vnode = routeResolver.render(vnode)
Аргумент | Тип | Описание |
---|---|---|
vnode | Object | Vnode, чей объект атрибутов содержит параметры маршрутизации. Если onmatch не возвращает компонент или Promise , который разрешается в компонент, поле tag vnode по умолчанию имеет значение "div" . |
vnode.attrs | Object | Карта значений параметров URL. |
возвращает | Array<Vnode>|Vnode | Vnodes, которые будут отображены. |
Параметр vnode
представляет собой m(Component, m.route.param())
, где Component
- это разрешенный компонент для маршрута (после routeResolver.onmatch
), а m.route.param()
- как описано здесь. Если вы опустите этот метод, значением возврата по умолчанию будет [vnode]
, обернутое во фрагмент, чтобы вы могли использовать ключевые параметры. В сочетании с параметром :key
он становится одноэлементным фрагментом с ключом, поскольку в конечном итоге он отображается во что-то вроде [m(Component, {key: m.route.param("key"), ...})]
.
Как это работает
Маршрутизация - это система, которая позволяет создавать одностраничные приложения (SPA), то есть приложения, которые могут переходить со "страницы" на другую, не вызывая полной перезагрузки браузера.
Она обеспечивает плавную навигацию, сохраняя при этом возможность добавления каждой страницы в закладки по отдельности и возможность навигации по приложению с помощью механизма истории браузера.
Маршрутизация без перезагрузки страниц частично стала возможной благодаря API history.pushState
. Используя этот API, можно программно изменить URL-адрес, отображаемый браузером после загрузки страницы, но разработчик приложения несет ответственность за то, чтобы навигация по любому заданному URL-адресу из "холодного" состояния (например, новой вкладки) отображала соответствующую разметку.
Стратегии маршрутизации
Стратегия маршрутизации определяет, как библиотека может фактически реализовать маршрутизацию. Существует три общие стратегии, которые можно использовать для реализации системы маршрутизации SPA, и каждая из них имеет разные особенности:
m.route.prefix = '#!'
(по умолчанию) - Использование части URL-адреса идентификатора фрагмента (также известного как хеш). URL-адрес, использующий эту стратегию, обычно выглядит какhttps://localhost/#!/page1
.m.route.prefix = '?'
- Использование строки запроса. URL-адрес, использующий эту стратегию, обычно выглядит какhttps://localhost/?/page1
.m.route.prefix = ''
- Использование имени пути. URL-адрес, использующий эту стратегию, обычно выглядит какhttps://localhost/page1
.
Использование хеш-стратегии гарантированно работает в браузерах, которые не поддерживают history.pushState
, поскольку она может вернуться к использованию onhashchange
. Используйте эту стратегию, если вы хотите, чтобы хеши были чисто локальными.
Стратегия строки запроса позволяет обнаруживать маршруты на стороне сервера, но URL не отображается как обычный путь. Используйте эту стратегию, если вы хотите поддерживать и потенциально обнаруживать привязанные ссылки на стороне сервера и не можете внести изменения, необходимые для поддержки стратегии имени пути (например, если вы используете Apache и не можете изменить свой .htaccess
).
Стратегия имени пути создает самые чистые URL-адреса, но требует настройки сервера для обслуживания кода одностраничного приложения из каждого URL-адреса, на который может маршрутизироваться приложение. Используйте эту стратегию для получения более чистых URL-адресов.
Одностраничные приложения, использующие хеш-стратегию, часто используют соглашение об использовании восклицательного знака после хеша, чтобы указать, что они используют хеш в качестве механизма маршрутизации, а не для целей ссылки на якоря. Строка #!
известна как hashbang.
Стратегия по умолчанию использует hashbang.
Типичное использование
Обычно вам нужно создать несколько компонентов для сопоставления маршрутов:
var Home = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
var Page1 = {
view: function () {
return [m(Menu), m('h1', 'Page 1')];
},
};
В приведенном выше примере есть два компонента: Home
и Page1
. Каждый содержит меню и некоторый текст. Само меню определяется как компонент, чтобы избежать повторения:
var Menu = {
view: function () {
return m('nav', [
m(m.route.Link, { href: '/' }, 'Home'),
m(m.route.Link, { href: '/page1' }, 'Page 1'),
]);
},
};
Теперь мы можем определить маршруты и сопоставить наши компоненты с ними:
m.route(document.body, '/', {
'/': Home,
'/page1': Page1,
});
Здесь мы указываем два маршрута: /
и /page1
, которые отображают соответствующие компоненты, когда пользователь переходит к каждому URL-адресу.
Переход к разным маршрутам
В приведенном выше примере компонент Menu
имеет два m.route.Link
. Это создает элемент, по умолчанию <a>
, и настраивает его так, что если пользователь щелкает по нему, он переходит к другому маршруту самостоятельно. Он не переходит удаленно, только локально.
Вы также можете перемещаться программно, через m.route.set(route)
. Например, m.route.set("/page1")
.
При переходе между маршрутами префикс маршрутизатора обрабатывается автоматически. Другими словами, опустите hashbang #!
(или любой префикс, который вы установили для m.route.prefix
), связывая маршруты Mithril.js, в том числе как в m.route.set
, так и в m.route.Link
.
Обратите внимание, что при переходе между компонентами происходит замена всего поддерева. Используйте route resolver с методом render
, если вы хотите просто обновить часть поддерева.
Параметры маршрутизации
Иногда мы хотим, чтобы в маршруте отображался переменный id
или аналогичные данные, но мы не хотим явно указывать отдельный маршрут для каждого возможного id
. Чтобы этого добиться, Mithril.js поддерживает параметризованные маршруты:
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)];
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
Это создает динамический маршрут, который соответствует любому URL, начинающемуся с /edit/
и за которым следуют данные. Затем значение id
сопоставляется как атрибут vnode компонента (vnode.attrs.id
).
В маршруте может быть несколько аргументов, например /edit/:projectID/:userID
даст свойства projectID
и userID
в объекте атрибутов vnode компонента.
Key (ключевой) параметр
Когда пользователь переходит с параметризованного маршрута на тот же маршрут с другим параметром (например, переходит с /page/1
на /page/2
при заданном маршруте /page/:id
), компонент не будет воссоздан с нуля, поскольку оба маршрута разрешаются в один и тот же компонент, и, таким образом, приведут к виртуальному dom in-place diff. Это имеет побочный эффект запуска хука onupdate
, а не oninit
/oncreate
. Однако разработчику довольно часто требуется синхронизировать воссоздание компонента с событием изменения маршрута.
Чтобы этого добиться, можно объединить параметризацию маршрута с ключами для очень удобного шаблона:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
Это означает, что vnode, который создается для корневого компонента маршрута, имеет объект параметра маршрута key
. Параметры маршрута становятся attrs
в vnode. Таким образом, при переходе с одной страницы на другую key
изменяется и приводит к воссозданию компонента с нуля (поскольку ключ сообщает движку виртуального dom, что старый и новый компоненты - это разные сущности).
Вы можете развить эту идею дальше, чтобы создавать компоненты, которые воссоздают себя при перезагрузке:
m.route.set(m.route.get(), {key: Date.now()})
Или даже использовать функцию history state
для достижения перезагружаемых компонентов, не загрязняя URL-адрес:
m.route.set(m.route.get(), null, {state: {key: Date.now()}})
Обратите внимание, что ключевой параметр работает только для маршрутов компонентов. Если вы используете route resolver, вам нужно будет использовать одноэлементный фрагмент с ключом, передав key: m.route.param("key")
, чтобы выполнить то же самое.
Вариативные маршруты
Также можно иметь вариативные маршруты, то есть маршрут с аргументом, который содержит имена путей URL-адресов, содержащие косые черты:
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
Обработка 404
Для изоморфного / универсального приложения JavaScript url-параметр и вариативный маршрут в сочетании очень полезны для отображения пользовательской страницы ошибки 404.
В случае ошибки 404 Not Found сервер отправляет обратно пользовательскую страницу клиенту. Когда загружается Mithril.js, он перенаправит клиента на маршрут по умолчанию, потому что он не может знать этот маршрут.
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
History state
Можно в полной мере воспользоваться базовым API history.pushState
, чтобы улучшить навигацию пользователя. Например, приложение может "запомнить" состояние большой формы, когда пользователь покидает страницу, переходя в другое место, так что если пользователь нажмет кнопку "Назад" в браузере, у него будет заполнена форма, а не пустая форма.
Например, вы можете создать форму следующим образом:
var state = {
term: '',
search: function () {
// сохраняем состояние для этого маршрута
// это эквивалентно `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// перейти
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term || ''; // заполняется из свойства `history.state`, если пользователь нажимает кнопку «Назад»
},
view: function () {
return m('form', [
m("input[placeholder='Search']", {
oninput: function (e) {
state.term = e.target.value;
},
value: state.term,
}),
m('button', { onclick: state.search }, 'Search'),
]);
},
};
m.route(document.body, '/', {
'/': Form,
});
Таким образом, если пользователь выполнит поиск и нажмет кнопку "Назад", чтобы вернуться в приложение, поле ввода по-прежнему будет заполнено поисковым запросом. Этот метод может улучшить пользовательский опыт в больших формах и других приложениях, где восстановление состояния вручную может быть затруднительным.
Изменение префикса маршрутизатора
Префикс маршрутизатора - это фрагмент URL-адреса, который определяет базовую стратегию, используемую маршрутизатором.
// установить стратегию pathname
m.route.prefix = '';
// установить стратегию querystring
m.route.prefix = '?';
// установить в хеш без восклицательного знака
m.route.prefix = '#';
// установить стратегию pathname на URL, не являющемся корневым
// например, если приложение находится по адресу `https://localhost/my-app`, а что-то другое находится по адресу `https://localhost`
m.route.prefix = '/my-app';
Расширенное разрешение компонентов
Вместо непосредственного сопоставления компонента с маршрутом, вы можете использовать объект RouteResolver
. Объект RouteResolver
содержит методы onmatch()
и/или render()
. Оба метода необязательны, но необходимо определить хотя бы один из них.
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath, route) {
return Home;
},
render: function (vnode) {
return vnode; // эквивалентно m(Home)
},
},
});
RouteResolver
'ы полезны для различных продвинутых сценариев маршрутизации.
Оборачивание компонента макета
Часто возникает необходимость обернуть все или большинство маршрутизируемых компонентов в переиспользуемый контейнер (обычно называемый "макетом"). Для этого сначала необходимо создать компонент, содержащий общую разметку, которая будет оборачивать другие компоненты:
var Layout = {
view: function (vnode) {
return m('.layout', vnode.children);
},
};
В приведенном выше примере макет состоит только из <div class="layout">
, который содержит дочерние элементы, переданные компоненту, но в реальном приложении он может быть гораздо сложнее.
Один из способов обернуть макет - определить анонимный компонент в карте маршрутов:
// example 1
m.route(document.body, '/', {
'/': {
view: function () {
return m(Layout, m(Home));
},
},
'/form': {
view: function () {
return m(Layout, m(Form));
},
},
});
Однако, поскольку компонент верхнего уровня является анонимным, переход с маршрута /
на маршрут /form
(или наоборот) приведет к удалению анонимного компонента и повторному созданию DOM. Если бы у компонента Layout
были определены методы жизненного цикла, хуки oninit
и oncreate
срабатывали бы при каждом изменении маршрута. В зависимости от приложения, это может быть желательным или нет.
Если вы хотите, чтобы компонент Layout
сравнивался на предмет изменений и сохранялся, а не пересоздавался с нуля, используйте RouteResolver
в качестве корневого объекта:
// example 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
В этом случае, если у компонента Layout
есть методы жизненного цикла oninit
и oncreate
, они будут срабатывать только при первом изменении маршрута (при условии, что все маршруты используют один и тот же макет).
Для иллюстрации, пример 1 эквивалентен следующему коду:
// functionally equivalent to example 1
var Anon1 = {
view: function () {
return m(Layout, m(Home));
},
};
var Anon2 = {
view: function () {
return m(Layout, m(Form));
},
};
m.route(document.body, '/', {
'/': {
render: function () {
return m(Anon1);
},
},
'/form': {
render: function () {
return m(Anon2);
},
},
});
Поскольку Anon1
и Anon2
являются разными компонентами, их поддеревья (включая Layout
) воссоздаются с нуля. Это также происходит, когда компоненты используются непосредственно, без RouteResolver
.
В примере 2, поскольку Layout
является компонентом верхнего уровня в обоих маршрутах, DOM для компонента Layout
дифференцируется (то есть остается нетронутым, если в нем нет изменений), и только изменение с Home
на Form
вызывает воссоздание этого подраздела DOM.
Перенаправление
Хук onmatch
в RouteResolver
можно использовать для выполнения логики до инициализации компонента верхнего уровня в маршруте. Вы можете использовать либо m.route.set()
Mithril, либо собственный API history
HTML. При использовании API history
для перенаправления, onmatch
должен возвращать Promise
, который никогда не будет выполнен, чтобы предотвратить обработку текущего маршрута. m.route.set()
отменяет разрешение соответствующего маршрута, поэтому это не требуется.
Пример: аутентификация
В приведенном ниже примере показано, как реализовать страницу авторизации, которая не позволяет пользователям видеть страницу /secret
, если они не вошли в систему.
var isLoggedIn = false;
var Login = {
view: function () {
return m('form', [
m(
'button[type=button]',
{
onclick: function () {
isLoggedIn = true;
m.route.set('/secret');
},
},
'Login'
),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!isLoggedIn) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
При загрузке приложения вызывается onmatch
, и поскольку isLoggedIn
имеет значение false
, приложение перенаправляется на /login
. После того, как пользователь нажмет кнопку входа в систему, isLoggedIn
будет установлено в true
, и приложение будет перенаправлено на /secret
. Хук onmatch
будет запущен еще раз, и поскольку isLoggedIn
на этот раз имеет значение true
, приложение отобразит компонент Home
.
Для простоты в приведенном выше примере статус входа пользователя в систему хранится в глобальной переменной, и этот флаг просто переключается, когда пользователь нажимает кнопку входа в систему. В реальном приложении пользователь должен будет предоставить надлежащие учетные данные для входа в систему, и нажатие кнопки входа в систему вызовет запрос к серверу для аутентификации пользователя:
var Auth = {
username: '',
password: '',
setUsername: function (value) {
Auth.username = value;
},
setPassword: function (value) {
Auth.password = value;
},
login: function () {
m.request({
url: '/api/v1/auth',
params: { username: Auth.username, password: Auth.password },
}).then(function (data) {
localStorage.setItem('auth-token', data.token);
m.route.set('/secret');
});
},
};
var Login = {
view: function () {
return m('form', [
m('input[type=text]', {
oninput: function (e) {
Auth.setUsername(e.target.value);
},
value: Auth.username,
}),
m('input[type=password]', {
oninput: function (e) {
Auth.setPassword(e.target.value);
},
value: Auth.password,
}),
m('button[type=button]', { onclick: Auth.login }, 'Login'),
]);
},
};
m.route(document.body, '/secret', {
'/secret': {
onmatch: function () {
if (!localStorage.getItem('auth-token')) m.route.set('/login');
else return Home;
},
},
'/login': Login,
});
Предварительная загрузка данных
Как правило, компонент может загружать данные при инициализации. Загрузка данных таким образом приводит к двукратной отрисовке компонента. Первый проход отрисовки происходит при маршрутизации, а второй - после того, как запрос завершится. Обратите внимание, что loadUsers()
возвращает Promise
, но любой Promise
, возвращаемый oninit
, в настоящее время игнорируется. Второй проход отрисовки происходит из background
option for m.request
.
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
oninit: state.loadUsers,
view: function () {
return state.users.length > 0
? state.users.map(function (user) {
return m('div', user.id);
})
: 'loading';
},
},
});
В приведенном выше примере при первой отрисовке пользовательский интерфейс отображает "loading"
, поскольку state.users
является пустым массивом до завершения запроса. Затем, как только данные становятся доступными, интерфейс обновляется и отображается список идентификаторов пользователей.
RouteResolver
'ы можно использовать для предварительной загрузки данных перед отрисовкой компонента, чтобы избежать мерцания интерфейса и, как следствие, избежать необходимости в индикаторе загрузки:
var state = {
users: [],
loadUsers: function () {
return m.request('/api/v1/users').then(function (users) {
state.users = users;
});
},
};
m.route(document.body, '/user/list', {
'/user/list': {
onmatch: state.loadUsers,
render: function () {
return state.users.map(function (user) {
return m('div', user.id);
});
},
},
});
В этом случае render
запускается только после завершения запроса, что делает использование тернарного оператора ненужным.
Разделение кода
В большом приложении может быть желательно загружать код для каждого маршрута по требованию, а не заранее. Разделение кодовой базы таким образом известно как разделение кода или отложенная загрузка. В Mithril.js это можно сделать, вернув Promise
из хука onmatch
:
В своей самой простой форме можно сделать следующее:
// Home.js
module.export = {
view: function () {
return [m(Menu), m('h1', 'Home')];
},
};
// index.js
function load(file) {
return m.request({
method: 'GET',
url: file,
extract: function (xhr) {
return new Function(
'var module = {};' + xhr.responseText + ';return module.exports;'
);
},
});
}
m.route(document.body, '/', {
'/': {
onmatch: function () {
return load('Home.js');
},
},
});
Однако, на практике, для работы в production-окружении, необходимо объединить все зависимости модуля Home.js
в один файл, который будет отдаваться сервером.
К счастью, существует ряд инструментов, которые облегчают задачу объединения модулей для отложенной загрузки. Вот пример использования native dynamic import(...)
, поддерживаемого многими упаковщиками:
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
Типизированные маршруты
В некоторых расширенных случаях маршрутизации может потребоваться ограничить значение параметра не только форматом пути, сопоставляя только что-то вроде числового идентификатора. Вы можете сделать это довольно легко, вернув m.route.SKIP
из маршрута.
m.route(document.body, '/', {
'/view/:id': {
onmatch: function (args) {
if (!/^\d+$/.test(args.id)) return m.route.SKIP;
return ItemView;
},
},
'/view/:name': UserView,
});
Скрытые маршруты
В редких случаях может потребоваться скрыть определенные маршруты для некоторых пользователей, но не для всех. Например, пользователю может быть запрещено просматривать определенного пользователя, и вместо отображения ошибки разрешения вы предпочли бы притвориться, что его не существует, и перенаправить на представление 404. В этом случае вы можете использовать m.route.SKIP
, чтобы просто притвориться, что маршрут не существует.
m.route(document.body, '/', {
'/user/:id': {
onmatch: function (args) {
return Model.checkViewable(args.id).then(function (viewable) {
return viewable ? UserView : m.route.SKIP;
});
},
},
'/:404...': PageNotFound,
});
Отмена/блокировка маршрута
RouteResolver
onmatch
может предотвратить разрешение маршрута, вернув Promise
, который никогда не выполняется. Это можно использовать для обнаружения попыток избыточного разрешения маршрута и их отмены:
m.route(document.body, '/', {
'/': {
onmatch: function (args, requestedPath) {
if (m.route.get() === requestedPath) return new Promise(function () {});
},
},
});
Интеграция со сторонними библиотеками
В некоторых случаях может потребоваться интеграция с другими фреймворками, такими как React. Вот как это сделать:
- Определите все маршруты, используя
m.route
как обычно, но убедитесь, что вы используете его только один раз. Использование нескольких точек монтирования маршрутов не поддерживается. - Когда вам нужно удалить подписки на маршрутизацию, используйте
m.mount(root, null)
, используя тот же root, который вы использовалиm.route(root, ...)
на.m.route
используетm.mount
внутри, чтобы подключить все, так что это не магия.
Вот пример с React:
class Child extends React.Component {
constructor(props) {
super(props);
this.root = React.createRef();
}
componentDidMount() {
m.route(this.root, '/', {
// ...
});
}
componentDidUnmount() {
m.mount(this.root, null);
}
render() {
return <div ref={this.root} />;
}
}
А вот примерный эквивалент с Vue:
<div ref="root"></div>
Vue.component('my-child', {
template: `<div ref="root"></div>`,
mounted: function () {
m.route(this.$refs.root, '/', {
// ...
});
},
destroyed: function () {
m.mount(this.$refs.root, null);
},
});