路由

定义路由

首先,当我们初始化 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和路由器实例的方法,在此方法中,您应该返回truefalse
{
  url: '/some-page/',
  master(app) {
    if (app.device.desktop) return true;
    return false;
  }
}
detailRoutes数组具有详细路由的数组
懒加载模块
modules数组在加载路由之前要加载的惰性模块数组
内容相关属性
以下路由属性定义了如何(从何处/什么)加载内容。
content字符串从指定的字符串内容创建动态页面。
url字符串通过 Ajax 加载页面内容。

还支持使用{{paramName}}表达式从路由路径中获取动态路由参数,例如:

{
  path: '/users/:userId/posts/:postId',
  url: 'http://myapp.com/posts/{{userId}}/{{postId}}'
}
component对象从传递的 Framework7 路由器组件加载页面。
componentUrl字符串通过 Ajax 将页面作为组件加载。

还支持使用{{paramName}}表达式从路由路径中获取动态路由参数。

asyncfunction(context)执行所需的异步操作,并返回所需的路由内容和选项。作为参数,它接收路由回调上下文对象。
asyncComponentfunction()

方法应返回已解析的 Promise,其中包含组件或具有包含组件的.default属性的 ES 模块。

它主要设计为async的简短版本,用于动态导入组件。例如

{
  path: '/some-page/',
  asyncComponent: () => import('./path/to/some-page.js'),
}
可路由选项卡
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字符串允许将页面路由作为模态框或面板打开。因此,它可以是以下之一:popuppopoverloginScreensheetpanel

路由回调上下文

asyncredirectbeforeEnterbeforeLeave 路由属性中使用的路由上下文回调的格式

属性
app指向全局应用程序实例的链接
to请求的路由
from当前活动的路由
router当前路由器实例
resolve调用以解析/继续路由的方法
reject调用以阻止/拒绝路由的方法
direction导航方向,可以是 forwardbackward

异步路由

async 路由属性是一个非常强大的工具,用于返回动态路由属性。它是一个具有以下参数的函数

async(context)

resolve 方法具有以下格式

resolve(parameters, options)

  • parameters object - 包含已解析路由内容的对象。必须包含 urlcontentcomponentcomponentUrl 属性之一
  • options object - 包含 路由选项 的对象

reject 回调函数没有参数

reject()

请注意,在异步方法中调用 resolvereject 之前,路由将被阻塞!

例如

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/ 页面上,并且有以下链接

  1. <a href="computers/">Computers</a> - 可以按预期工作。链接将与当前路由 (/catalog/ + computers/) 合并,我们将获得 /catalog/computers/,这在我们的路由中存在。

  2. <a href="./computers/">Computers</a> - 与情况 1 的工作方式相同,因为路径开头的 ./ 表示相同的子级别。

  3. <a href="/catalog/computers/">Computers</a> - 也将按预期工作,与情况 1 相同,因为开头的 /(斜杠)表示根目录。并且我们在合并的路由中拥有此类根路由。

  4. <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',
      },
    ],
  },
  // ...
]

可路由选项卡

可路由选项卡是什么意思,为什么好?

首先,我们需要在应用路由中指定选项卡路由。假设我们在 /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-activetab-active 类。这些类和选项卡将由路由器切换。并且有一个新的 data-route-tab-id 属性,选项卡切换器需要它来理解哪个链接与所选路由相关。

您可以在 选项卡 组件页面的相应部分了解有关可路由选项卡及其附加事件的更多信息。

可路由模态框

模态框也可以是可路由的。这里的模态框是指以下组件:弹出窗口弹出框操作表登录屏幕工作表模态框。弹出窗口和登录屏幕可能在这里有更多用例。

与可路由选项卡和页面相同的功能

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'
          } ,
        });
      }
    },
  }
]

根据上面的例子

可路由面板

可路由面板从 Framework7 版本 3.2.0 开始提供。

面板(侧边面板)也可以像可路由模态框和页面一样进行路由

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>
      */
    },
  },
]

根据上面的例子

请注意,**可路由面板不能与静态面板混合使用**。因此,如果您的应用程序中有静态左侧面板,则只有右侧面板可以作为可路由面板加载。

路由进入/离开前

如果您需要在路由加载(进入)和卸载(离开)之前进行其他检查、执行其他操作或加载/发送某些内容,则 beforeEnterbeforeLeave 路由钩子非常有用。它可以是要执行的单个方法或方法数组。例如

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 将无法正常工作**,在这种情况下,您应该使用 asyncasyncComponent 属性在触发 beforeEnter 后动态加载页面

重定向和别名

别名

我们可以使用路由的 alias 属性传递路由别名。在这种情况下,别名基本上意味着相同的路由可以有多个 路径 来访问

routes = [
  {
    path: '/foo/',
    url: 'somepage.html',
    alias: '/bar/',
  },
  {
    path: '/foo2/',
    url: 'anotherpage.html',
    alias: ['/bar2/', '/baz/', '/baz2/'],
  }
]

根据上面的例子

重定向

我们可以使用 redirect 属性传递路由重定向

例如

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();
    }
  }
]

上面的例子意味着

请注意,在重定向中,我们传递的是 URL,而不是像别名那样的路由路径

保持活动状态

keepAlive 路由从 Framework7 版本 3.6.0 开始可用。

启用 keepAlive 后,一旦路由器加载了此类页面,该页面及其组件(如果有)(Vue、React 或路由器组件)将永远不会被销毁。相反,它将从 DOM 中分离并在需要时再次使用。

对于不经常更新的“重”页面,启用它会很有用。例如,包含地图或包含大量画布或其他计算的页面。按照通常的逻辑,每次访问此页面时都会进行所有这些繁重的计算。但是使用 keepAlive,它将完成一次,并且在下一次访问时,路由器将重用已经渲染的页面 DOM 元素。

如果您确实需要保留页面状态,它也会很有用。启用 keepAlive 后,下次加载页面时,将保留所有 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,
  },
];