路由
定义路由
首先,当我们初始化 Framework7 应用时,我们应该使用 routes
数组参数传递默认路由
var app = new Framework7({
routes: [
{
name: 'about',
path: '/about/',
url: './pages/about.html',
},
{
name: 'news',
path: '/news/',
url: './pages/news.html',
options: {
animate: false,
},
},
{
name: 'users',
path: '/users/',
componentUrl: './pages/users.html',
options: {
props: {
users: ['John Doe', 'Vladimir Kharlampidi', 'Timo Ernst'],
},
},
on: {
pageAfterIn: function test (e, page) {
// do something after page gets into the view
},
pageInit: function (e, page) {
// do something when page initialized
},
}
},
// Default route, match to all pages (e.g. 404 page)
{
path: '(.*)',
url: './pages/404.html',
},
],
});
在应用初始化时定义的路由是默认路由,它们将可用于应用中的任何视图/路由器。
如果您有一个多视图/路由应用程序,并且您希望某些视图/路由具有自己的严格路由并且不希望默认路由在此视图中可用,那么您可以在视图初始化时指定相同的routes
参数。
var view1 = app.views.create('.view-1', {
routes: [
{
path: '/users/',
url: './pages/users.html',
},
{
path: '/user/',
url: './pages/user.html',
},
],
});
如果您有一个多视图/路由应用程序,并且您希望某些视图/路由具有附加路由,并且不希望这些附加路由在其他视图中可用,那么您可以在视图初始化时指定routesAdd
参数。
// This view will support all global routes + own additional routes
var view2 = app.views.create('.view-2', {
// These routes are only available in this view
routesAdd: [
{
path: '/blog/',
url: './pages/blog.html',
},
{
path: '/post/',
url: './pages/post.html',
},
],
})
路由属性
好的,现在我们将看看每个路由属性的含义
参数 | 类型 | 描述 | |
---|---|---|---|
name | 字符串 | 路由名称,例如home | |
path | 字符串 | 路由路径。表示当我们点击与此路径匹配的链接时,或通过使用 API 加载此路径时,将加载此路由。 | |
options | 对象 | 具有附加路由选项的对象(可选) | |
routes | 数组 | 具有嵌套路由的数组 | |
viewName | 字符串 | 将强制加载此路由的视图名称 | |
主从视图 | |||
master | 布尔值 function(app, router) | 将此路由启用为主路由。它也可以是一个接收app 和路由器实例的方法,在此方法中,您应该返回true 或false 。
| |
detailRoutes | 数组 | 具有详细路由的数组 | |
懒加载模块 | |||
modules | 数组 | 在加载路由之前要加载的惰性模块数组 | |
内容相关属性 以下路由属性定义了如何(从何处/什么)加载内容。 | |||
content | 字符串 | 从指定的字符串内容创建动态页面。 | |
url | 字符串 | 通过 Ajax 加载页面内容。 还支持使用
| |
component | 对象 | 从传递的 Framework7 路由器组件加载页面。 | |
componentUrl | 字符串 | 通过 Ajax 将页面作为组件加载。 还支持使用 | |
async | function(context) | 执行所需的异步操作,并返回所需的路由内容和选项。作为参数,它接收路由回调上下文对象。 | |
asyncComponent | function() | 方法应返回已解析的 Promise,其中包含组件或具有包含组件的 它主要设计为
| |
可路由选项卡 | |||
tabs | 数组 | 具有选项卡路由的数组 | |
可路由模态框 | |||
actions | 对象 | 操作表路由 | |
popup | 对象 | 弹出框路由 | |
loginScreen | 对象 | 登录屏幕路由 | |
popover | 对象 | 弹出窗口路由 | |
sheet | 对象 | 表单路由 | |
可路由面板 | |||
panel | 对象 | 面板路由 | |
事件 | |||
on | 对象 | 具有事件处理程序的对象 | |
别名 & 重定向 | |||
alias | 字符串 数组 | 路由别名,或路由别名数组。我们需要在此处指定别名路径。 | |
redirect | 字符串 函数(上下文) | 路由重定向。我们需要在此指定重定向 **网址**(不是路径)。如果是方法,则它会接收 路由回调上下文 对象作为参数。 | |
进入/离开前 | |||
beforeEnter | 函数(上下文) 数组 | 将在路由加载/进入之前执行的函数(或函数数组)。要继续路由加载,必须调用 resolve 。如果是 数组 ,则必须解析数组中的每个函数才能继续。如果是方法,则它会接收 路由回调上下文 对象作为参数。 | |
beforeLeave | 函数(上下文) 数组 | 将在路由卸载/离开之前执行的函数(或函数数组)。要继续导航,必须调用 resolve 。如果是 数组 ,则必须解析数组中的每个函数才能继续。如果是方法,则它会接收 路由回调上下文 对象作为参数。 | |
keepAlive | |||
keepAlive | 布尔值 | 启用所谓的 keepAlive 路由。启用后,加载的页面及其组件(Vue、React 或 Router 组件)将永远不会被销毁。相反,它将从 DOM 中分离并在需要时再次使用。 |
以下是大多数可能选项的示例
routes: [
// Load via Ajax
{
path: '/about/',
url: './pages/about.html',
},
// Dynamic page from content
{
path: '/news/',
content: `
<div class="page">
<div class="page-content">
<div class="block">
<p>This page created dynamically</p>
</div>
</div>
</div>
`,
},
// By page name (data-name="services") presented in DOM
{
path: '/services/',
pageName: 'services',
},
// By page HTMLElement
{
path: '/contacts/',
el: document.querySelector('.page[data-name="contacts"]'),
},
// By component
{
path: '/posts/',
component: {
// look below
},
},
// By component url
{
path: '/post/:id/',
componentUrl: './pages/component.html',
},
// Async
{
path: '/something/',
async: function ({ app, to, resolve }) {
// Requested route
console.log(to);
// Get external data and return page content
fetch('http://some-endpoint/')
.then((res) => res.json())
.then(function (data) {
resolve(
// How and what to load
{
content: `<div class="page">${data.users}</div>`
},
);
});
}
}
],
路由路径
如上所述,路由的 path
属性是指在加载以下路由时(通过 api 或单击具有相同路径的链接)将显示在浏览器窗口地址栏中的路径/网址(如果启用了 browserHistory
)。
还支持动态路径。因此,如果您的路由 /blog/users/:userId/posts/:postId/
中有以下路径,并且单击了 /blog/users/12/posts/25
href 的链接,则在加载的页面上,我们可以访问包含 { userId: 12, postId: 25 }
的 route.params
对象
路由路径匹配由 Path To Regexp 库处理,因此 Framework7 也支持该库支持的所有内容。例如,如果要添加匹配所有路径的默认路由,我们可以使用如下正则表达式
// Default route, match to all pages (e.g. 404 page)
{
path: '(.*)',
url: './pages/404.html',
},
路由选项
让我们看一下可以在 options
属性中传递的其他路由选项
参数 | 类型 | 描述 |
---|---|---|
动画 | 布尔值 | 页面是否应具有动画效果(覆盖默认路由器设置) |
历史记录 | 布尔值 | 页面是否应保存在路由器历史记录中 |
浏览器历史记录 | 布尔值 | 页面是否应保存在浏览器状态中。如果您使用的是 browserHistory ,则可以在此处传递 false 以防止路由进入浏览器历史记录 |
reloadCurrent | 布尔值 | 用路由中的新页面替换当前页面,在这种情况下没有动画 |
reloadPrevious | 布尔值 | 用路由中的新页面替换历史记录中的上一页 |
reloadAll | 布尔值 | 加载新页面并从历史记录和 DOM 中删除所有先前页面 |
clearPreviousHistory | 布尔值 | 重新加载/导航到指定路由后,将清除以前的页面历史记录 |
ignoreCache | 布尔值 | 如果设置为 true 则会忽略缓存中的 URL 并使用 XHR 重新加载 |
force | 布尔值 | 如果设置为 true 则会忽略历史记录中的上一页并加载指定的页面 |
props | 对象 | 将作为 Vue/React 页面组件 props 传递的 props |
transition | 字符串 | 自定义页面过渡名称 |
openIn | 字符串 | 允许将页面路由作为模态框或面板打开。因此,它可以是以下之一:popup 、popover 、loginScreen 、sheet 、panel |
路由回调上下文
在 async
、redirect
、beforeEnter
和 beforeLeave
路由属性中使用的路由上下文回调的格式
属性 | |
---|---|
app | 指向全局应用程序实例的链接 |
to | 请求的路由 |
from | 当前活动的路由 |
router | 当前路由器实例 |
resolve | 调用以解析/继续路由的方法 |
reject | 调用以阻止/拒绝路由的方法 |
direction | 导航方向,可以是 forward 或 backward |
异步路由
async
路由属性是一个非常强大的工具,用于返回动态路由属性。它是一个具有以下参数的函数
async(context)
- context - 路由回调上下文
resolve
方法具有以下格式
resolve(parameters, options)
- parameters object - 包含已解析路由内容的对象。必须包含
url
、content
、component
或componentUrl
属性之一 - options object - 包含 路由选项 的对象
reject
回调函数没有参数
reject()
请注意,在异步方法中调用 resolve
或 reject
之前,路由将被阻塞!
例如
routes = [
{
path: '/foo/',
async({ resolve, reject }) {
if (userIsLoggedIn) {
resolve({ url: 'secured.html' })
} else {
resolve({ url: 'login.html' })
}
}
}
]
路由事件
可以使用 on
路由属性将所有页面事件添加到该页面的路由中。例如
var app = new Framework7({
routes: [
// ...
{
path: '/users/',
url: './pages/users.html',
on: {
pageBeforeIn: function (event, page) {
// do something before page gets into the view
},
pageAfterIn: function (event, page) {
// do something after page gets into the view
},
pageInit: function (event, page) {
// do something when page initialized
},
pageBeforeRemove: function (event, page) {
// do something before page gets removed from DOM
},
}
},
// ...
],
});
请注意,此类路由事件实际上是 DOM 事件,因此每个此类处理程序都将接受 event
作为第一个参数(事件本身)和 page
作为第二个参数(页面数据)。
此外,此类事件处理程序的上下文 (this
) 将指向相关的 路由器实例。
嵌套路由
也可以有嵌套路由(路由中的路由)
routes = [
{
path: '/faq/',
url: './pages/faq.html',
},
{
path: '/catalog/',
url: './pages/catalog.html',
routes: [
{
path: 'computers/',
url: './pages/computers.html',
},
{
path: 'monitors/',
url: './pages/monitors.html',
},
...
],
}
];
这意味着什么?为了更好地理解,实际上(在底层)此类路由将合并到以下路由中
routes = [
{
path: '/faq/',
url: './pages/faq.html',
},
{
path: '/catalog/',
url: './pages/catalog.html',
}
{
path: '/catalog/computers/',
url: './pages/computers.html',
},
{
path: '/catalog/monitors/',
url: './pages/monitors.html',
},
];
假设我们在 /catalog/
页面上,并且有以下链接
<a href="computers/">Computers</a>
- 可以按预期工作。链接将与当前路由 (/catalog/
+computers/
) 合并,我们将获得/catalog/computers/
,这在我们的路由中存在。<a href="./computers/">Computers</a>
- 与情况 1 的工作方式相同,因为路径开头的./
表示相同的子级别。<a href="/catalog/computers/">Computers</a>
- 也将按预期工作,与情况 1 相同,因为开头的/
(斜杠)表示根目录。并且我们在合并的路由中拥有此类根路由。<a href="/computers/">Computers</a>
- 无法按预期工作,因为开头的/
(斜杠)表示根目录。并且我们在路由中没有此类/computers/
根路由。
详细路由
对于主从视图,除了主路由上的 master: true
之外,还可以指定 detailRoutes
。
指定 detailRoutes
后,导航到详细路由也将预加载其主路由。
但与嵌套路由(在 routes
参数中指定)不同,详细路由 path
不会与主路由 path
合并。
routes = [
{
path: '/blog/',
url: './news.html',
master: true,
detailRoutes: [
{
/* We need to specify detail route path from root */
path: '/blog/:postId/',
url: './post.html',
},
],
},
// ...
]
可路由选项卡
可路由选项卡是什么意思,为什么好?
- 首先,它提供了通过常用链接而不是所谓的特殊选项卡链接导航到选项卡的机会。
- 其次,导航到此类路由时,您可以加载打开所需选项卡的页面。
- 第三,启用浏览器历史记录后,在历史记录中后退和前进时,将打开相同的选项卡。
- 最后但同样重要的是,使用可路由选项卡时,您可以使用与页面相同的方式加载选项卡内容,即使用
url
、content
、component
或componentUrl
首先,我们需要在应用路由中指定选项卡路由。假设我们在 /tabs/ 路由上有一个带有可路由选项卡的页面
routes = [
{
path: '/about-me/',
url: './pages/about-me/index.html',
// Pass "tabs" property to route
tabs: [
// First (default) tab has the same url as the page itself
{
path: '/',
id: 'about',
// Fill this tab content from content string
content: `
<div class="block">
<h3>About Me</h3>
<p>...</p>
</div>
`
},
// Second tab
{
path: '/contacts/',
id: 'contacts',
// Fill this tab content via Ajax request
url: './pages/about-me/contacts.html',
},
// Third tab
{
path: '/cv/',
id: 'cv',
// Load this tab content as a component via Ajax request
componentUrl: './pages/about-me/cv.html',
},
],
}
]
例如,在 /about-me/
页面上,我们可能具有以下结构
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">About Me</div>
</div>
</div>
<div class="toolbar tabbar toolbar-bottom">
<div class="toolbar-inner">
<a href="./" class="tab-link" data-route-tab-id="about">About</a>
<a href="./contacts/" class="tab-link" data-route-tab-id="contacts">>Contacts</a>
<a href="./cv/" class="tab-link" data-route-tab-id="cv">>CV</a>
</div>
</div>
<div class="tabs tabs-routable">
<div class="tab page-content" id="about"></div>
<div class="tab page-content" id="contacts"></div>
<div class="tab page-content" id="cv"></div>
</div>
</div>
与通常的 选项卡 几乎相同,但区别在于选项卡链接和选项卡上不再有 tab-link-active
和 tab-active
类。这些类和选项卡将由路由器切换。并且有一个新的 data-route-tab-id
属性,选项卡切换器需要它来理解哪个链接与所选路由相关。
您可以在 选项卡 组件页面的相应部分了解有关可路由选项卡及其附加事件的更多信息。
可路由模态框
模态框也可以是可路由的。这里的模态框是指以下组件:弹出窗口、弹出框、操作表、登录屏幕、工作表模态框。弹出窗口和登录屏幕可能在这里有更多用例。
与可路由选项卡和页面相同的功能
- 它提供了通过常用链接而不是所谓的特殊链接或 API 打开模态框的机会,
- 启用浏览器历史记录后,刷新浏览器、在历史记录中后退和前进时,将打开相同的模态框,
- 使用可路由模态框,您可以像页面一样加载模态框本身及其内容,即使用
url
、content
、component
或componentUrl
routes = [
...
// Creates popup from passed HTML string
{
path: '/popup-content/',
popup: {
content: `
<div class="popup">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
`
}
},
// Load Login Screen from file via Ajax
{
path: '/login-screen-ajax/',
loginScreen: {
url: './login-screen.html',
/* login-screen.html contains:
<div class="login-screen">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
*/
},
},
// Load Popup from component file
{
path: '/popup-component/',
loginScreen: {
componentUrl: './popup-component.html',
/* popup-component.html contains:
<template>
<div class="popup-screen">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
</template>
<style>...</style>
<script>...</script>
*/
},
},
// Use async route to check if the user is logged in:
{
path: '/secured-content/',
async({ resolve }) {
if (userIsLoggedIn) {
resolve({
url: 'secured-page.html',
});
} else {
resolve({
loginScreen: {
url: 'login-screen.html'
} ,
});
}
},
}
]
根据上面的例子
- 当您点击带有
/popup-content/
href 属性的链接时,它将从指定的字符串内容打开弹出窗口, - 当您点击带有
/login-screen-ajax/
href 属性的链接时,它将执行对login-screen.html
文件的 Ajax 请求,并将其作为登录屏幕打开, - 当您点击带有
/popup-component/
href 属性的链接时,它将执行对popup-component.html
文件的 Ajax 请求,将其解析为路由器组件并将其作为弹出窗口打开, - 当您点击带有
/secured-content/
href 属性的链接时,如果用户已登录,它将从secured-page.html
加载页面,如果用户未登录,则从login-screen.html
文件打开登录屏幕。
可路由面板
可路由面板从 Framework7 版本 3.2.0 开始提供。
面板(侧边面板)也可以像可路由模态框和页面一样进行路由
- 它提供了通过常用链接而不是所谓的特殊链接或 API 打开面板的机会,
- 启用浏览器历史记录后,当您刷新浏览器、在历史记录中后退和前进时,将打开同一个面板,
- 使用可路由面板,您可以像页面和模态框一样加载面板本身及其内容,即使用
url
、content
、component
或componentUrl
routes = [
...
// Creates Panel from passed HTML string
{
path: '/left-panel/',
panel: {
content: `
<div class="panel panel-left panel-cover">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
`
}
},
// Load Panel from file via Ajax
{
path: '/right-panel-ajax/',
panel: {
url: './right-panel.html',
/* right-panel.html contains:
<div class="panel panel-right panel-reveal">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
*/
},
},
// Load Panel from component file
{
path: '/panel-component/',
panel: {
componentUrl: './panel-component.html',
/* panel-component.html contains:
<template>
<div class="panel panel-left panel-cover">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
</template>
<style>...</style>
<script>...</script>
*/
},
},
]
根据上面的例子
- 当您点击带有
/left-panel/
href 属性的链接时,它将从指定的字符串内容打开面板, - 当您点击带有
/right-panel-ajax/
href 属性的链接时,它将执行对right-panel.html
文件的 Ajax 请求,并将其作为右侧面板打开, - 当您点击带有
/panel-component/
href 属性的链接时,它将执行对panel-component.html
文件的 Ajax 请求,将其解析为路由器组件并将其作为面板打开,
请注意,**可路由面板不能与静态面板混合使用**。因此,如果您的应用程序中有静态左侧面板,则只有右侧面板可以作为可路由面板加载。
路由进入/离开前
如果您需要在路由加载(进入)和卸载(离开)之前进行其他检查、执行其他操作或加载/发送某些内容,则 beforeEnter
和 beforeLeave
路由钩子非常有用。它可以是要执行的单个方法或方法数组。例如
routes = [
{
path: 'profile',
url: 'profile.html',
beforeEnter: function ({ resolve, reject }) {
if (/* some condition to check user is logged in */) {
resolve();
} else {
// don't allow to visit this page for unauthenticated users
reject();
}
},
},
{
path: 'profile-edit',
url: 'profile-edit.html',
beforeLeave: function ({ resolve, reject }) {
if (/* user didn't save edited form */) {
app.dialog.confirm(
'Are you sure you want to leave this page without saving data?',
function () {
// proceed navigation
resolve();
},
function () {
// stay on page
reject();
}
)
} else {
resolve();
}
}
}
]
当然,当作为函数数组传递时,支持多个钩子
function checkAuth({ to, from, resolve, reject }) {
if (/* some condition to check user is logged in */) {
resolve();
} else {
reject();
}
}
function checkPermission({ to, from, resolve, reject }) {
if (/* some condition to check user edit permission */) {
resolve();
} else {
reject();
}
}
routes = [
{
path: '/profile/',
url: 'profile.html',
// check if the user is logged in
beforeEnter: checkAuth,
},
{
path: '/profile-edit/',
url: 'profile-edit.html',
// check if the user is logged in and has required permission
beforeEnter: [checkAuth, checkPermission],
}
]
请注意,**如果使用 component
属性加载页面,则 beforeEnter
将无法正常工作**,在这种情况下,您应该使用 async
或 asyncComponent
属性在触发 beforeEnter
后动态加载页面
重定向和别名
别名
我们可以使用路由的 alias
属性传递路由别名。在这种情况下,别名基本上意味着相同的路由可以有多个 路径 来访问
routes = [
{
path: '/foo/',
url: 'somepage.html',
alias: '/bar/',
},
{
path: '/foo2/',
url: 'anotherpage.html',
alias: ['/bar2/', '/baz/', '/baz2/'],
}
]
根据上面的例子
- 如果我们通过
/foo/
或/bar/
URL 请求页面,那么第一个路由将匹配,我们将从somepage.html
加载页面 - 如果我们通过
/foo2/
、/bar2/
、/baz/
、/baz2/
URL 请求页面,那么第二个路由将匹配,我们将从anotherpage.html
加载页面
重定向
我们可以使用 redirect
属性传递路由重定向
- 如果我们传递
redirect
作为string
,我们必须在此处传递直接重定向 URL - 如果我们传递
redirect
作为function
,我们需要使用重定向 URL 调用函数的 resolve 参数
例如
routes = [
{
path: '/foo/',
url: 'somepage.html',
},
{
path: '/bar/',
redirect: '/foo/',
},
{
path: '/baz/',
redirect: function ({to, resolve, reject}) {
// if we have "user" query parameter
if (to.query.user) {
// redirect to such url
resolve('/foo/?user=' + to.query.user);
}
// otherwise do nothing
else reject();
}
}
]
上面的例子意味着
- 当我们请求
/bar/
URL 时,路由器将重定向到/foo/
URL,然后搜索与此新 URL 匹配的路由。在这种情况下,它将匹配路径为/foo/
的第一个路由并从somepage.html
加载页面 - 当我们请求
/baz/?user=john
时,我们将重定向到 URL/foo/?user=john
,该 URL 将与第一个路由匹配 - 当我们请求
/baz/
(没有查询)时,什么也不会发生
请注意,在重定向中,我们传递的是 URL,而不是像别名那样的路由路径
保持活动状态
keepAlive 路由从 Framework7 版本 3.6.0 开始可用。
启用 keepAlive
后,一旦路由器加载了此类页面,该页面及其组件(如果有)(Vue、React 或路由器组件)将永远不会被销毁。相反,它将从 DOM 中分离并在需要时再次使用。
对于不经常更新的“重”页面,启用它会很有用。例如,包含地图或包含大量画布或其他计算的页面。按照通常的逻辑,每次访问此页面时都会进行所有这些繁重的计算。但是使用 keepAlive,它将完成一次,并且在下一次访问时,路由器将重用已经渲染的页面 DOM 元素。
如果您确实需要保留页面状态,它也会很有用。启用 keepAlive 后,下次加载页面时,将保留所有 DOM 修改和表单元素状态。
但有些事情要注意
- 不支持
async
路由 - 它仅支持页面(不支持面板和模态)
- 如果您有动态路由路径(如
/some-page/:foo/:bar
)或依赖于页面路由查询(?foo=bar
),则查询和路由参数在其初始加载后不会更改。 - 对于此类页面,永远不会触发
page:beforeremove
、pageBeforeRemove
页面事件 - 如果您将其用作 Vue 组件,则永远不会为此类组件触发
beforeDestroy
、destroyed
钩子。created
、mounted
钩子将只触发一次。 - 如果您将其用作 React 组件,则永远不会为此类组件触发
componentWillUnmount
方法。componentWillMount
、componentDidMount
方法将只触发一次。 - 如果您将它用作 F7 的路由组件,则该组件的
beforeDestroy
和destroyed
钩子将永远不会被触发。created
和mounted
钩子只会被触发一次。
为了避免组件和页面生命周期中的陷阱,建议您依赖以下页面事件:
page:mounted
- 当 keepAlive 路由页面的 DOM 元素被附加或重新附加时,将始终调用此事件。page:beforeunmount
- 当 keepAlive 路由页面的 DOM 元素将要从 DOM 中分离时,将始终调用此事件。
要创建 keepAlive 路由,我们只需要将其参数中的 keepAlive
设置为 true
即可。
import SomPageComponent from './some-page.js';
var routes = [
/* Usual route */
{
path: '/',
url: './pages/home.html',
},
/* Alive route. Will be loaded from file first time, and then will reuse rendered DOM element */
{
path: '/map/',
url: './pages/map.html',
keepAlive: true,
},
/* Alive route. Will be created as component, and then will reuse rendered DOM element */
{
path: '/some-page/',
component: SomPageComponent,
keepAlive: true,
},
];