路由

定义路由

首先,当我们初始化 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
路径字符串路由路径。这意味着当点击匹配此路径的链接时,将加载此路由,或者可以使用此路径通过 API 加载
选项对象包含附加路由选项的对象(可选)
路由数组包含嵌套路由的数组
视图名称字符串此路由将在其中强制加载的视图名称
主从
master布尔值
function(app, router)
将此路由启用为主路由。它也可以是一个接收app和路由实例的方法,在这个方法中,你应该返回truefalse:
{
  url: '/some-page/',
  master(app) {
    if (app.device.desktop) return true;
    return false;
  }
}
从路由数组包含从路由的数组
懒加载模块
模块数组包含懒加载模块在路由加载之前要加载的模块
内容相关属性
以下路由属性定义了内容应该如何加载(从哪里/什么)
内容字符串从指定的内容字符串创建动态页面
url字符串通过 Ajax 加载页面内容。

也支持从路径使用{{paramName}}表达式动态路由参数,例如

{
  path: '/users/:userId/posts/:postId',
  url: 'http://myapp.com/posts/{{userId}}/{{postId}}'
}
组件上传递单独的属性对象从传递的 Framework7路由组件
组件Url字符串通过 Ajax 作为组件加载页面

也支持从路径使用{{paramName}}表达式

异步函数(context)执行必要的异步操作,并返回所需的路由内容和选项。它接收路由回调上下文对象的静态属性访问。
异步组件function()

方法应该返回解析为组件或包含.default属性的 ES 模块。

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

{
  path: '/some-page/',
  asyncComponent: () => import('./path/to/some-page.js'),
}
可路由选项卡
tabs数组包含选项卡路由的数组
可路由模态框
动作对象动作表路由
弹出对象弹出路由
loginScreen对象登录屏幕路由
弹出对象弹出路由
表格对象表格路由
可路由面板
面板对象面板路由
事件
on对象包含事件处理器的对象
别名和重定向
alias字符串
数组
路由别名,或包含路由别名的数组。我们需要在这里指定别名路径
redirect字符串
function(context)
路由重定向。我们需要在这里指定重定向url(不是路径)。如果是方法,则作为参数接收路由回调上下文对象的静态属性访问。
进入/离开之前
beforeEnterfunction(context)

数组
将在路由加载/进入之前执行的函数(或函数数组)。要继续路由加载resolve必须被调用。如果是array,则数组中的每个函数都必须解析以继续。如果是方法,则作为参数接收路由回调上下文对象的静态属性访问。
beforeLeavefunction(context)

数组
将在路由卸载/离开之前执行的函数(或函数数组)。要继续导航resolve必须被调用。如果是array,则数组中的每个函数都必须解析以继续。如果是方法,则作为参数接收路由回调上下文对象的静态属性访问。
keepAlive
keepAlive布尔值启用所谓的keepAlive路由。启用后,一旦加载页面及其组件(Vue、React 或路由组件)将永远不会被销毁。相反,它将从 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启用)显示的路径/URL。

还支持动态路径。因此,如果你的路由中有以下路径/blog/users/:userId/posts/:postId/并且点击具有/blog/users/12/posts/25href 的链接,然后在加载的页面上我们访问route.params包含{ userId: 12, postId: 25 }

路径匹配由Path To Regexp库处理,因此那里支持的所有内容在 Framework7 中也支持。例如,如果你想添加一个匹配所有路径的默认路由,我们可以使用正则表达式:

// Default route, match to all pages (e.g. 404 page)
{
  path: '(.*)',
  url: './pages/404.html',
},

路由选项

让我们看看可以传递在options派发动作

参数类型描述
animate布尔值中的附加路由选项,是否应该对页面进行动画处理(覆盖默认路由器设置)
history布尔值是否应该将页面保存到路由历史中
browserHistory布尔值是否应该将页面保存到浏览器状态中。如果你使用browserHistory,则可以在这里传递false以防止路由进入浏览器历史
reloadCurrent布尔值用路由中的新页面替换当前页面,此情况下没有动画
reloadPrevious布尔值用路由中的新页面替换历史中的上一页
reloadAll布尔值加载新页面并从历史记录和 DOM 中删除所有上一页
clearPreviousHistory布尔值重新加载/导航到指定路由后,上一页的历史记录将被清除
ignoreCache布尔值如果设置为true,则它将忽略缓存中的此类 URL,并再次使用 XHR 重新加载它
force布尔值如果设置为true,则它将忽略历史中的上一页,并加载指定的页面
props对象将作为 Vue/React 页面组件属性传递的属性
transition字符串自定义页面过渡名称
openIn字符串允许将页面路由作为模态或面板打开。所以它可以是以下之一:popup, popover, loginScreen, sheet, panel

路由回调上下文

async, redirect, beforeEnterbeforeLeave路由属性中使用的路由上下文回调格式:

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

异步路由

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

async(context)

resolve路由回调的方法具有以下格式:

resolve(参数,选项)

  • 参数 对象- 包含解析路由内容的对象。必须包含以下之一url, content, componentcomponentUrl属性
  • 选项 对象- 包含路由选项

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> - 将不会按预期工作,因为(斜杠)在开头意味着根。我们没有这样的/ (slash) in the beginning means root. And we don't have such /computers/根路由在我们的路由中。

详细路由

对于主从视图,也可以在主路由上指定detailRoutes除了master: true在主路由上。

detailRoutes指定时,导航到详细路由也会预加载其主路由。

但与在routes参数中指定的嵌套路由不同,详细路由paths 不会与主路由合并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],
  }
]

注意beforeEnter如果页面使用component属性加载,则无法正常工作,在这种情况下,您应该使用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,而不是像别名中那样传递路由路径

Keep Alive

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

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

对于不经常更新的“重型”页面启用它可能很有用。例如,带有地图的页面,或带有重型画布或其他计算的页面。使用常规逻辑,所有这些重型计算都会在每次访问此页面时发生。但使用 keepAlive,它将只执行一次,并在所有后续访问中,路由器将重用已经渲染的页面 DOM 元素。

如果您确实需要保留页面状态,启用 keepAlive 也很有用。在启用 keepAlive 的情况下,下次加载页面时,所有 DOM 修改和表单元素状态都将被保留。

但要注意以下几点:

为了避免组件和页面生命周期中的陷阱,建议依赖以下页面事件:

要创建 keepAlive 路由,我们只需要将其参数传递给keepAlive: true to its parameters:

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