路由

定義路由

首先,當我們初始化 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}} 表達式的動態路由參數

非同步函式(context)執行必要的非同步處理,並傳回必要的路由內容和選項。它會收到 路由回呼內容 物件作為參數。
asyncComponent函式()

方法應該傳回 Promise,解析為包含元件的 Component 或 ES 模組,其 .default 屬性包含 Component。

它主要設計為 async 的簡短版本,用於動態匯入元件。例如

{
  path: '/some-page/',
  asyncComponent: () => import('./path/to/some-page.js'),
}
可路由標籤
tabs陣列包含標籤路由的陣列
可路由模態視窗
actions物件動作表單路由
popup物件快顯視窗路由
loginScreen物件登入畫面路由
popover物件浮動視窗路由
sheet物件工作表路由
可路由面板
panel物件面板路由
事件
on物件包含事件處理常式的物件
別名和重新導向
alias字串
陣列
路由別名,或包含路由別名的陣列。我們需要在此處指定別名 路徑
redirect字串
函式(context)
路由重新導向。我們需要在此處指定重新導向 網址(非路徑)。如果為方法,則它會收到 路由回呼內容 物件作為參數。
進入/離開前
beforeEnter函式(context)

陣列
函式(或函式陣列),將在路由載入/進入前執行。若要繼續路由載入,必須呼叫 resolve。如果是 陣列,則必須解析陣列中的每個函式才能繼續。如果為方法,則它會收到 路由回呼內容 物件作為參數。
beforeLeave函式(context)

陣列
函式(或函式陣列),將在路由卸載/離開前執行。若要繼續導覽,必須呼叫 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 屬性表示將在瀏覽器視窗網址列中顯示的路徑/網址(如果啟用 browserHistory),當透過 API 或按一下具有相同路徑的連結載入下列路由時。

也支援動態路徑。因此,如果您的路由中有下列路徑 /blog/users/:userId/posts/:postId/,並按一下網址為 /blog/users/12/posts/25 的連結,則在載入的頁面上,我們可以存取包含 { 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 屬性中傳遞的其他路由選項

參數類型說明
animate布林值頁面是否應有動畫(覆寫預設路由器設定)
history布林值頁面是否應儲存在路由器歷程記錄中
browserHistory布林值頁面是否應儲存在瀏覽器狀態中。如果您正在使用 browserHistory,則可以在這裡傳遞 false 以防止路由進入瀏覽器歷程記錄
reloadCurrent布林值使用路由中的新頁面取代當前頁面,此情況下沒有動畫
reloadPrevious布林值使用路由中的新頁面取代歷程記錄中的前一頁
reloadAll布林值載入新頁面,並從歷程記錄和 DOM 中移除所有前一頁
clearPreviousHistory布林值重新載入/導航至指定路由後,將清除前一頁的歷程記錄
ignoreCache布林值如果設為 true,則會忽略快取中是否有此類 URL,並再次使用 XHR 重新載入
force布林值如果設為 true,則會忽略歷程記錄中的前一頁,並載入指定的頁面
props物件將作為 Vue/React 頁面元件屬性傳遞的屬性
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,
  },
];