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。 |
返回值 | 无返回值。 |
静态成员
m.route.set
重定向到匹配的路由,如果找不到匹配的路由,则重定向到默认路由。触发所有挂载点的异步重绘。
m.route.set(path, params, options)
参数 | 类型 | 必需 | 描述 |
---|---|---|---|
path | String | 是 | 要跳转到的路径名,不包含前缀。路径中可以包含参数,这些参数会使用 params 中的值进行替换。 |
params | Object | 否 | 路由参数。如果 path 包含路由参数占位符,则此对象的属性值将替换路径字符串中的占位符。 |
options.replace | Boolean | 否 | 是否创建新的历史记录条目或替换当前条目。默认为 false 。 |
options.state | Object | 否 | 传递给底层 history.pushState / history.replaceState 调用的 state 对象。该对象会保存在 history.state 属性中,并合并到路由参数对象中。注意:只有在使用 pushState API 时,此选项才有效;如果路由器回退到 hashchange 模式(例如,pushState API 不可用),此选项会被忽略。 |
options.title | String | 否 | 要传递给底层 history.pushState / history.replaceState 调用的 title 字符串。 |
返回值 | 无返回值。 |
请注意,使用 .set
和 params
时,还需要定义路由:
var Article = {
view: function (vnode) {
return 'This is article ' + vnode.attrs.articleid; // 显示文章ID: + 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 | 否 | 设置传递给 m.route.set 的 options 。 |
attributes.params | Object | 否 | 设置传递给 m.route.set 的 params 。 |
attributes | Object | 否 | 要转发到 m 的任何其他属性。 |
children | Array<Vnode>|String|Number|Boolean | 否 | 此链接的子 vnode。 |
返回值 | 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 | 否 | 路由参数名称(例如,路由 /users/:id 中的 id ,或路径 /users/1?page=3 中的 page ,或 history.state 中的键)。 |
返回值 | String|Object | 返回指定键对应的值。如果未指定键,则返回包含所有键值对的对象。 |
请注意,在 RouteResolver
的 onmatch
函数中,新路由尚未完全解析,并且 m.route.param()
将返回先前路由的参数(如果有)。onmatch
接收新路由的参数作为参数。
m.route.SKIP
一个特殊的值,可以从路由解析器的 onmatch
中返回,用于跳过当前路由,进入下一个路由。
RouteResolver
RouteResolver
是一个非组件对象,它包含 onmatch
方法和/或 render
方法。这两个方法都是可选的,但至少要存在一个。
如果一个对象可以被检测为组件(通过存在 view
方法或通过作为 function
/class
),即使它具有 onmatch
或 render
方法,它也会被视为组件。由于 RouteResolver
不是组件,因此它没有生命周期方法。
通常,RouteResolver
应该与 m.route
调用放在同一个文件中,而组件定义应该放在单独的模块中。
routeResolver = {onmatch, render}
当使用组件时,您可以将它们视为此路由解析器的特殊语法糖,假设您的组件是 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,则此组件用作 RouteResolver
的 render
方法中第一个参数的 vnode.tag
。否则,vnode.tag
设置为 "div"
。类似地,如果省略 onmatch
方法,则 vnode.tag
也为 "div"
。
如果 onmatch
返回一个被拒绝的 promise,则路由器将重定向回 defaultRoute
。您可以通过在返回 promise 链之前调用 .catch
来覆盖此行为。
routeResolver.render
对于匹配的路由,每次重绘都会调用 render
方法。它类似于组件中的 view
方法,它的存在是为了简化 组件组合。它还允许你避免 Mithril.js 默认替换整个子树的行为。
vnode = routeResolver.render(vnode)
参数 | 类型 | 描述 |
---|---|---|
vnode | Object | 一个 vnode,其属性对象包含路由参数。如果 onmatch 不返回组件或解析为组件的 promise,则 vnode 的 tag 字段默认为 "div" 。 |
vnode.attrs | Object | URL 参数值的映射。 |
返回值 | Array<Vnode>|Vnode | 要渲染的 vnode。 |
vnode
参数只是 m(Component, m.route.param())
,其中 Component
是路由的已解析组件(在 routeResolver.onmatch
之后),m.route.param()
如 此处 所述。如果省略该方法,默认返回值是 [vnode]
,会被包裹在一个 Fragment 中,以便使用键参数。与 :key
参数结合使用,它将成为一个 单元素键控片段,因为它最终会渲染成类似 [m(Component, {key: m.route.param("key"), ...})]
的东西。
工作原理
路由是一种用于创建单页应用程序 (SPA) 的机制,它允许在不同的“页面”之间切换,而无需刷新整个浏览器。
它实现了无缝导航,同时保留了单独为每个页面添加书签的能力,以及通过浏览器的历史记录机制导航应用程序的能力。
在不刷新页面的情况下进行路由部分地由 history.pushState
API 实现。使用此 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")
。
在路由之间导航时,路由器会自动处理前缀。换句话说,在链接 Mithril.js 路由时,请省略 hashbang #!
(或您设置的 m.route.prefix
前缀),包括在 m.route.set
和 m.route.Link
中。
请注意,在组件之间导航时,整个子树会被替换。如果只想更新部分子树,可以使用带有 render
方法的路由解析器。
路由参数
有时,我们希望在路由中包含可变的 ID 或其他数据,但又不想为每个可能的 ID 都定义一个单独的路由。为了实现这个目的,Mithril.js 提供了参数化路由功能:
var Edit = {
view: function (vnode) {
return [m(Menu), m('h1', 'Editing ' + vnode.attrs.id)]; // 正在编辑ID为: + vnode.attrs.id + 的内容
},
};
m.route(document.body, '/edit/1', {
'/edit/:id': Edit,
});
在上面的示例中,我们定义了一个路由 /edit/:id
。这将创建一个动态路由,该路由匹配任何以 /edit/
开头并后跟一些数据的 URL(例如,/edit/1
、edit/234
等)。然后,id
值将映射为组件的 vnode 的属性 (vnode.attrs.id
)。
可以在路由中定义多个参数,例如,对于路由 /edit/:projectID/:userID
,组件 vnode 的属性对象上会包含 projectID
和 userID
两个属性。
键参数
当用户从一个参数化路由导航到另一个参数不同的相同路由时(例如,从 /page/1
切换到 /page/2
,假设路由定义为 /page/:id
),组件默认不会重新创建,因为这两个路由会解析到同一个组件,从而触发虚拟 DOM 的就地更新。这样做会导致触发 onupdate
钩子,而不是 oninit
或 oncreate
钩子。然而,开发者通常希望组件的重新创建与路由变化事件同步。
为此,可以将路由参数化与 键 结合使用,以实现方便的模式:
m.route(document.body, '/edit/1', {
'/edit/:key': Edit,
});
这意味着为路由的根组件创建的 vnode 会包含一个名为 key
的路由参数对象。路由参数会成为 vnode 的 attrs
属性。因此,当从一个页面跳转到另一个页面时,key
的值会发生变化,从而导致组件重新创建(因为 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()}})
请注意,键参数仅适用于组件路由。如果使用路由解析器,则需要使用单元素键控片段,并传递 key: m.route.param("key")
来实现相同的效果。
可变路由
也可以使用可变路由,即可变路由的参数可以包含带有斜杠的 URL 路径名:
m.route(document.body, '/edit/pictures/image.jpg', {
'/edit/:file...': Edit,
});
处理 404
对于同构/通用 JavaScript 应用,URL 参数和可变路由的组合非常适合用于显示自定义 404 错误页面。
当发生 404 Not Found 错误时,服务器会返回自定义的 404 页面给客户端。当 Mithril.js 加载后,由于无法识别该路由,它会将客户端重定向到默认路由。
m.route(document.body, '/', {
'/': homeComponent,
// [...]
'/:404...': errorPageComponent,
});
历史记录状态
可以充分利用底层的 history.pushState
API 来提升用户的导航体验。例如,应用程序可以在用户离开页面时“记住”大型表单的状态,这样,当用户点击浏览器的后退按钮时,表单内容仍然存在,而不是显示一个空白表单。
例如,您可以创建一个像这样的表单:
var state = {
term: '',
search: function () {
// save the state for this route
// this is equivalent to `history.replaceState({term: state.term}, null, location.href)`
m.route.set(m.route.get(), null, {
replace: true,
state: { term: state.term },
});
// navigate away
location.href = 'https://google.com/?q=' + state.term;
},
};
var Form = {
oninit: function (vnode) {
state.term = vnode.attrs.term || ''; // populated from the `history.state` property if the user presses the back button
},
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 的一部分,它决定了路由器使用的底层 策略。
// set to pathname strategy
m.route.prefix = ''; // 设置为路径名模式
// set to querystring strategy
m.route.prefix = '?'; // 设置为查询字符串模式
// set to hash without bang
m.route.prefix = '#'; // 设置为不带 bang 的哈希
// set to pathname strategy on a non-root URL
// e.g. if the app lives under `https://localhost/my-app` and something else
// lives under `https://localhost`
m.route.prefix = '/my-app'; // 在非根 URL 下设置为路径名模式
高级组件解析
除了将组件直接关联到路由之外,你还可以指定一个 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">
,用于容纳传递给组件的子元素。但在实际场景中,它可以根据需要变得非常复杂。
一种包装布局的方法是在路由映射中定义一个匿名组件:
// 示例 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
作为根对象:
// 示例 2
m.route(document.body, '/', {
'/': {
render: function () {
return m(Layout, m(Home));
},
},
'/form': {
render: function () {
return m(Layout, m(Form));
},
},
});
请注意,在这种情况下,如果 Layout
组件具有 oninit
和 oncreate
生命周期方法,它们将仅在第一次路由更改时触发(假设所有路由都使用相同的布局)。
为了更清楚地说明这两个示例之间的区别,示例 1 等效于以下代码:
// 在功能上等效于示例 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
是两个路由中的顶层组件,因此 Layout
组件的 DOM 会通过差异化算法进行更新(即,如果没有更改,则保持不变),只有从 Home
到 Form
的更改才会触发 DOM 中相应部分的重新创建。
重定向
RouteResolver 的 onmatch
钩子可用于在路由中的顶层组件初始化之前执行一些逻辑。你可以使用 Mithril 的 m.route.set()
或原生 HTML 的 history
API。当使用 history
API 进行重定向时,onmatch
钩子必须返回一个永远不会 resolve 的 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,但是目前 oninit
返回的任何 Promise 都会被忽略。第二次渲染来自 m.request
的 background
选项。
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';
},
},
});
在上面的示例中,在第一次渲染时,UI 显示 "loading"
,因为在请求完成之前 state.users
是一个空数组。然后,一旦数据可用,UI 就会重绘并显示用户 ID 列表。
RouteResolvers 可以用作一种机制,在渲染组件之前预加载数据,以避免 UI 闪烁,从而避免显示加载指示器:
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 中,可以通过从 onmatch
钩子返回一个 Promise 来实现。
在最基本的形式下,可以执行以下操作:
// 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');
},
},
});
然而,实际上,为了使其在生产环境中正常工作,有必要将 Home.js
模块的所有依赖项打包到最终由服务器提供的文件中。
幸运的是,有很多工具可以简化打包模块以进行延迟加载的任务。这是一个使用 原生动态 import(...)
的示例,许多打包器都支持它:
m.route(document.body, '/', {
'/': {
onmatch: function () {
return import('./Home.js');
},
},
});
类型化的路由
在某些高级路由场景下,你可能希望对路由参数进行更严格的约束,而不仅仅是路径本身,例如只允许匹配数字 ID。你可以通过从路由返回 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
方法可以通过返回一个永远不会 resolve 的 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);
},
});