路由

定义路由

首先,当我们初始化 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/',
      templateUrl: './pages/users.html',
      options: {
        context: {
          users: ['John Doe', 'Vladimir Kharlampidi', 'Timo Ernst'],
        },
      },
      on: {
        pageAfterIn: function (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',
    },
  ],
});

在应用初始化时定义的路由为默认路由,他们将适用于应用内的所有视图(View)或路由(Router)。

如果你正在开发的是一款多视图(multi-view)或多路由应用,同时希望每个视图(View)有独立的路由机制 own strict routes ,并且不想让全局的默认路由机制应用到这个视图 View, 那么,你可以在视图(View)初始化时使用同样的数组参数 routes :

var view1 = app.views.create('.view-1', {
  routes: [
    {
      path: '/users/',
      url: './pages/users.html',
    },
    {
      path: '/user/',
      url: './pages/user.html',
    },
  ],
});

如果你正在开发的是一款多视图(multi-view)或多路由应用,同时希望某些视图有部分 额外路由 ,并且这些额外路由不同应用到别的视图, 那么你可以在视图初始化时使用参数 routesAdd:

// 这个视图支持全局路由 + 自有额外路由
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 string 路由名称, e.g. home
path string 路由路径。表示我们点击路由匹配的链接或者通过 API 调用时,将会加载这个路由路径对应路径的文件
options object 路由额外的可选参数对象(可选)
routes array 路由嵌套数组
内容相关参数
以下路由参数将说明内容将如何(从哪里或从什么)被加载
el HTMLElement 从 DOM里通过 HTML 元素加载页面
pageName string 通过指定的 data-name 属性值加载页面
content string 使用指定的内容创建动态页面
url string 通过调用 Ajax 加载页面

同时支持通过 route path 使用 {{paramName}} 表达式来实现动态路由, 例如:

{
  path: '/users/:userId/posts/:postId',
  url: 'http://myapp.com/posts/{{userId}}/{{postId}}'
}
template string
function
通过 Template7 模板字符串或方法加载页面内容
templateUrl string 通过 Ajax 异步请求加载页面内容,同时使用 Template7 编译该页面

同时支持使用 {{paramName}} 表达式的动态路由 route path

component object 通过 Framework7 的路由组件 Router Component 加载页面内容
componentUrl string 通过 Ajax 异步请求将页面当做一个组件来加载

同时支持使用 {{paramName}} 表达式的动态路由 route path

async function(routeTo, routeFrom, resolve, reject) 异步处理路由从哪跳转到哪的操作,包含了处理通过的操作和拒绝处理的操作(有更好的翻译请联系译者)(Do required asynchronous manipulation and the return required route content and options)
支持路由的标签栏
tabs array 路由标签数组参数
支持路由的模态框
actions object 上拉操作列表路由
popup object 弹出框路由
loginScreen object 登录框路由
popover object 悬浮框路由
sheet object 操作列表路由
事件
on object 事件处理对象
别名 & 重定向
alias string
array
路由别名或路由数组别名。我们需要通过 path 指定别名
redirect string
function(route, resolve, reject)
路由重定向。我们需要通过 url 指定重定向 url (not path)

以下是一个比较完整的案例:

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 template
  {
    path: '/template/:name/',
    template: `
      <div class="page">
        <div class="page-content">
          <div class="block">
            <p>Hello {{$route.params.name}}</p>
          </div>
        </div>
      </div>
    `,
  },
  // By template URL
  {
    path: '/blog/',
    templateUrl: './pages/blog.html',
  },
  // By component
  {
    path: '/posts/',
    component: {
      // look below
    },
  },
  // By component url
  {
    path: '/post/:id/',
    componentUrl: './pages/component.html',
  },
  // Async
  {
    path: '/something/',
    async: function (routeTo, routeFrom, resolve, reject) {
      // Requested route
      console.log(routeTo);
      // Get external data and return template7 template
      app.request.json('http://some-endpoint/', function (data) {
        resolve(
          // How and what to load: template
          {
            template: '<div class="page">{{users}}</div>'
          },
          // Custom template context
          {
            context: {
              users: data,
            },
          }
        );
      });
    }
  }
],

路由路径

如上所述,路由的 path 参数表示当点击路由链接或通过 api 加载路由的时候,同时如果开启了 pushState ,那么浏览器的地址栏上将会显示同样的路由 path/url 。

这里同样支持动态路由。所以如果你设置了如 /blog/users/:userId/posts/:postId/ 的路由,并且点击了 /blog/users/12/posts/25 的链接,那么将会在页面的 route.params 读取包含参数 { userId: 12, postId: 25 } 的对象。

路由路径的匹配操作依赖于 Path To Regexp 库,所以只要 Path To Regexp 库支持的, Framework7 里同样支持。举个例子,如果你想要新增一个匹配所有路径的路由,可以使用以下的正则表达式来实现:

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

路由可选参数

我们可以通过属性 options 来设置路由的可选属性:

参数 类型 描述
animate boolean 页面是否启用动画效果(会覆盖全局默认路由的设置)
history boolean 页面是否会保存到路由历史里
pushState boolean 页面是否会保存到浏览器的状态里。 将 pushState 设置为 false ,可以防止路由将当前页面保存到浏览器的浏览历史里。
reloadCurrent boolean 是否使用路由请求过来的新页面替换当前页面
reloadPrevious boolean 是否使用路由请求过来的新页面替换浏览器历史的上一个页面
reloadAll boolean 是否使用路由请求过来的新页面替换浏览器历史的其他所有页面
context object Template/Component 页面自定义或扩展的上下文 (当使用 template, templateUrl, component or componentUrl 加载路由时)

异步路由

async 是一个专门为返回动态路由参数而设计的强大工具。它的方法包含了如下的参数:

async(routeTo, routeFrom, resolve, reject)

  • routeTo - 即将请求的路由
  • routeFrom - 当前所在路由
  • resolve - 正常执行路由时的回调方法
  • reject - 中断执行路由时的回调方法

resolve 调用这个回调方法支持传入两个参数:

resolve(parameters, options)

  • parameters object - 包含即将请求的路由的内容。至少包含所列属性中的一个: url, content, template, templateUrl, component or componentUrl
  • options object - 可选路由属性 Route Options

reject 此回调方法不包含任何参数:

reject()

注意,除非你在 async 方法中执行了 resolvereject ,否则路由请求将会中断!

例如:

routes = [
  {
    path: '/foo/',
    async(routeTo, routeFrom, resole, 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 data 的页面对象 page

嵌套路由

以下展示的就是嵌套路由的示例代码:

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 - 有效链接。 路由链接将会合并当前路由 (/catalog/ + computers/) ,最后的效果和我们在路由配置文件中定义的 /catalog/computers/ 一样。

  2. <a href="./computers/">Computers</a> - 效果和示例 1 一样 ./ 放在开头,表示同样的路径层级。

  3. <a href="/catalog/computers/">Computers</a> - 效果和示例 1 一样。 因为 / (斜线) 放在开头表示根目录。同时在我们合并后的路由表里,有对应的 <a href="/catalog/computers/">Computers</a> 路由(推荐使用此方式,比较清晰明了)

  4. <a href="/computers/">Computers</a> - 无效链接 因为 / (斜线) 放在开头表示根目录。但是我们并没有在路由表的根路由里设置路由 /computers/

标签页路由

什么是标签页路由和标签页路由的优点?

  • 首先,它提供了一种使用通常的链接路由的方式跳转标签页的方式,取代了以往的旧的需要设置一个特殊的标签页的方式。
  • 其次,当导航到指定的路由时,你可以直接使用路由链接打开指定的标签页内容。
  • 第三,当你开启 Push State 的时候,同样可以使用浏览器浏览历史的前进、后退来切换之前打开过的标签页。
  • 最后也是同样重要的,当你使用支持路由的标签页时,你可以像加载页面一样的来加载标签页的内容,例如: 使用这几个参数 url, content, template, templateUrl, component or 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-inner">
      <div class="title">About Me</div>
    </div>
  </div>
  <div class="toolbar tabbar">
    <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>

Tabs 以往的用法不太一样了,现在不需要在标签页的 links 和 tabs 上加额外的样式类 tab-link-activetab-active ,这些样式将会通过路由自动切换。另外,多了一个属性参数 data-route-tab-id ,这个参数的作用是指定当前路由链接要跳转到哪个选中的路由。

打开这个链接 Tabs 可以了解更多关于路由标签页的属性、配置、事件等。

模态框路由

模态框也是可以通过路由来访问的。这里所说的模态框,指的是以下几个组件:Popup, Popover, Actions Sheet, Login Screen, Sheet。相对而言,弹出框和登录框我们更加常见。

其拥有和 标签页路由 和 页面路由 同样的特性:

  • 它提供了通过一种使用常规路由链接的方式打开模态框,取代了以往使用特殊的链接或 API 调用打开模态框的方式,
  • 同样的,如果开启了 Push State,在浏览器刷新、前进、后退的时候,可以打开对应的模态框,
  • 模态框路由可以像页面路由一样加载指定的内容,例如: 使用 url, content, template, templateUrl, component or 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-page/',
    async(routeTo, routeFrom, resolve, reject) {
      if (userIsLoggedIn) {
        resolve({
          url: 'secured-page.html',
        });
      } else {
        resolve({
          loginScreen: {
            url: 'login-screen.html'
          } ,
        });
      }
    },
  }
]

解释上面的例子:

  • 当你点击 href 属性值为 /popup-content/ 的链接时,将会打开弹出框,并展示指定的内容,
  • 当你点击 href 属性值为 /login-screen-ajax/ 的链接时,将会发起一个 Ajax 请求文件 login-screen.html 并以登录框的形式打开,
  • 当你点击 href 属性值为 /popup-component/ 的链接时,将会发起一个 Ajax 请求文件 popup-component.html ,将其解析为一个路由器组件并通过弹出框的方式打开,
  • 当你点击 href 属性值为 /secured-content/ 的链接时,如果用户已经登录,将会加载页面 secured-page.html ,否则会加载页面 login-screen.html

重定向 & 别名

别名

我们可以使用路由的 alias 属性来实现路由别名。路由别名指的是同一个路由目标 paths,可以通过多个路由路径来访问:

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

解释上面的例子:

  • 当我们通过 URL 请求页面 /foo//bar/ 时,第一个路由配置将会匹配并加载页面 somepage.html
  • 当我们通过 URL 请求页面 /foo2/ , /bar2/, /baz/, /baz2/ 时,第二个路由配置将会匹配并加载页面 anotherpage.html

重定向

我们可以使用路由的 redirect 属性来实现路由的重定向:

  • 如果我们使用 string 来设置属性 redirect,我们必须指定具体重定向的目标 URL
  • 如果我们使用 function 来设置属性 redirect,我们将需要通过调用方法来处理重定向

例如:

routes = [
  {
    path: '/foo/',
    url: 'somepage.html',
  },
  {
    path: '/bar/',
    redirect: '/foo/',
  },
  {
    path: '/baz/',
    redirect: function (route, resolve, reject) {
      // if we have "user" query parameter
      if (route.query.user) {
        // redirect to such url
        resolve('/foo/?user=' + route.query.user);
      }
      // otherwise do nothing
      else reject();
    }
  }
]

解释上面的例子:

  • 当我们通过 URL 请求 /bar/ 时,路由将会重定向到 URL /foo/ 并搜索路由来匹配新的 URL 。在这个例子里,路由将会匹配到第一个路由配置 /foo/ 并最终加载页面 somepage.html
  • 当我们通过 URL 请求 /baz/?user=john 时,路由将会重定向到 URL /foo/?user=john ,路由将会匹配到第一个路由配置
  • 当我们通过 URL 请求 /baz/ (不带请求参数) 时,路由将不会进行重定向

注意:路由重定向,使用的是 URL,而不是路由别名里的 path