1. 程式人生 > >Vue躬行記(8)——Vue Router

Vue躬行記(8)——Vue Router

  雖然Vue.js未提供路由功能,但是官方推出了Vue Router(即vue-router庫),以外掛的形式支援。它與Vue.js深度整合,可快速的建立單頁應用(Single Page Application,SPA)。

一、基本用法

  首先需要引入vue和vue-router兩個庫,如果像下面這樣全域性引用(即存在全域性變數Vue),那麼vue-router會自動呼叫Vue.use()方法註冊其自身;但如果以模組的方式引用,那麼就得顯式地呼叫Vue.use()。

<script src="js/vue.js"></script>
<script src="js/vue-router.js"></script>

  然後新增兩個內建的元件,第一個是導航用的router-link元件,它預設會被渲染成<a>元素,如果要渲染成其它元素,那麼可以使用它的tag屬性;第二個是路徑匹配時用於渲染檢視的router-view元件,它還是一個函式式元件,如下所示。

<div id="container">
  <router-link to="/main">首頁</router-link>
  <router-link to="/list">列表</router-link>
  <router-view></router-view>
</div>

  再宣告要渲染的元件,這些元件會與指定的路由對映,下面是兩個非常簡單的自定義元件。

const Main = { template: '<div>首頁</div>' };
const List = { template: '<div>列表</div>' };

  接著定義路由配置,新增一組路徑以及對應的元件,如下所示。

const routes = [
  { path: '/main', component: Main },
  { path: '/list', component: List }
];

  最後建立路由器例項,並將之前的路由配置傳遞進來,在掛載根例項時,需要將它注入,從而讓整個應用都有路由功能,如下所示。

const router = new VueRouter({
  routes: routes
});
var vm = new Vue({
  el: "#container",
  router: router
});

  注意,router例項包含三個導航方法:push()、replace()和go(),可以用程式設計的方式進行導航。

  頁面在渲染完成後,得到的DOM結構如下所示,此時router-view元件所佔的位置無任何內容。

<div id="container">
  <a href="#/main" class="">首頁</a>
  <a href="#/list" class="">列表</a>
</div>

  當router-link元件所對應的路由匹配成功時,其渲染出的元素會被自動新增一個CSS類:router-link-active。例如點選首頁連結,得到的結構如下所示,注意,此時router-view元件被替換成了Main元件中的內容。

<div id="container">
  <a href="#/main" class="router-link-active">首頁</a>
  <a href="#/list" class="">列表</a>
  <div>首頁</div>
</div>

二、路由模式

  Vue Router預設採用URL hash模式來保持頁面和URL的同步,其建立的URL格式需要包含井號(#),如下所示。

http://pwstrick.com/#/main

  Vue Router還有另外一種history模式,利用HTML5 History來保持頁面和URL的同步,其建立的URL格式在視覺上更為簡潔清晰,如下所示。

http://pwstrick.com/main

  如果要開啟history模式,那麼需要在路由配置時新增mode選項,並賦予history關鍵字,如下所示(直接修改了上一節中的示例)。

const router = new VueRouter({
  mode: "history",
  routes: routes
});

  注意,當直接訪問history模式建立的URL時,會返回一個404頁面。為了避免這種情況的發生,建議在伺服器上配置一個預設的候選頁面。

三、路由

  本節不僅會介紹動態路由的概念,還會分析路由的命名和巢狀等操作。

1)動態路由

  Vue Router採用的路徑匹配引擎是path-to-regexp,它支援動態路由的匹配,例如有一個List元件,需要根據路徑中的頁碼引數把元件渲染成不同的內容,可以像下面這樣配置。

const List = { template: '<div>{{$route.params.page}}</div>' };
const routes = [
  { path: '/list/:page', component: List }
];

  在路由配置中,以冒號開頭的:page是路徑引數,它的值會被儲存到路由物件$route的params屬性中。路由物件不僅包含解析URL得到的資訊(例如路徑、查詢字串、錨點等),還有匹配到的路由資訊(例如路由名稱、路由記錄等)。當路徑是/list/1時,$route.params的值為{page: 1},params是一個物件,其鍵值與路徑引數對應。注意,一條路徑可以包含多個不同的路徑引數。

  當一個路徑能匹配多個路由時,匹配優先順序會按照路由的定義順序來排,即先定義的,優先順序高。下面的配置包含兩個路由,當路徑是/list/1時,只會渲染List元件。

const routes = [
  { path: '/list/:page', component: List },
  { path: '/list/1', component: Main }
];

2)命名路由

  可以在配置時為路由標識一個名稱,從而就能在使用時省略路徑了,如下所示,name屬性的值就是路由名稱。

const routes = [
  { path: '/list/:page', component: List, name: "list" }
];

  如果要導航到一個命名路由,那麼需要動態地繫結to屬性,併為其傳入路由的名稱以及可選的URL引數,如下所示。

<router-link :to="{ name: 'list', params: {page: 3} }">第三頁</router-link>

3)巢狀路由

  Vue Router允許巢狀路由,可通過多層巢狀的元件實現。下面是一個使用示例,首先在一個元件中宣告router-view檢視元件,如下所示。

const List = { template: '<router-view></router-view>' };
const Detail = { template: '<div>詳情</div>' };

  然後在路由配置中,新增children選項,並定義子路由。

const routes = [
  {
    path: "/list",
    component: List,
    children: [{ path: "detail", component: Detail }]
  }
];

  在經過這一系列操作後,當路徑是/list/detail時,Detail元件就會被渲染到List元件的檢視中。

  巢狀路由可用來渲染多個不同層級的檢視,而利用命名檢視可渲染多個同級的檢視。在下面的程式碼中,為第二個router-view元件添加了name屬性,賦予它一個名稱,而第一個router-view元件的預設名稱為default。

<router-view></router-view>
<router-view name="detail"></router-view>

  在路由配置時,新增用於記錄各個命名元件的components選項,如下所示。

const routes = [
  {
    path: "/named",
    components: {
      default: Main,
      detail: Detail
    }
  }
];

  當路徑是/named時,Main和Detail兩個元件會被同時渲染。

四、重定向和別名

1)重定向

  在路由配置中,通過redirect選項可重定向到一個新的頁面,該選項可取的值有三種,分別是路徑、命名路由和回撥函式,如下所示,其中函式中的to引數表示目標路由物件。

const routes = [
  { path: "/list/1", redirect: "/main" },
  { path: "/list/1", redirect: {name: "main"} },
  { path: "/list/1", redirect: to => "/main" }
];

  當訪問的路徑是/list/1時,瀏覽器的位址列會變成/main,匹配的將是路徑為/main的路由。

2)別名

  在路由配置中,通過alias選項可定義路由的別名,如下所示。

const routes = [
  { path: "/list/4", component: Detail, alias: "/detail" }
];

  當訪問的路徑是/main時,瀏覽器的位址列保持為/main,但匹配的將是路徑為/list/4的路由。

五、元件傳參

  除了在元件中通過$route.params獲取路由引數之外,還能將路由引數作為元件的props傳入,從而將元件和路由解耦,如下所示。

const List = {
  props: ["page"],
  template: "<div>{{page}}</div>"
};
const routes = [
  { path: "/list/:page", component: List, props: true }
];

  需要注意的是,在路由配置時要新增props選項,並賦予true後,才會讓路由引數和props關聯。

六、導航守衛

  導航守衛就是路由發生變化時的鉤子函式,Vue Router提供了三類守衛:全域性、路由獨享和元件級。

1)全域性守衛

  在路由器例項上可註冊三個全域性守衛,分別是beforeEach()、beforeResolve()和afterEach()。

  beforeEach()是一個全域性前置守衛,包含三個引數(如下所示),其中to是目標路由物件、from是來源路由物件,next是一個回撥函式,用於解析(resolve)當前守衛。

router.beforeEach((to, from, next) => {
  next();
});

  由於導航在所有守衛解析之前會處於等待狀態(即不會改變路徑,也不會渲染對應的元件),因此在回撥中不能省略next()函式。注意,該函式不僅能處理導航,還能將其中斷或重定向,甚至註冊一個錯誤。

  beforeResolve()是一個全域性解析守衛,其引數和beforeEach()的相同,在導航被確認前,並且元件內的守衛和非同步路由元件被解析之後呼叫。afterEach()是一個全域性後置守衛,它只有to和from兩個引數。

2)路由守衛

  在路由配置中可直接定義beforeEnter()守衛,作用於某個單獨的路由而非全域性,其引數和beforeEach()的相同,如下所示。

const routes = [
  {
    path: "/list",
    component: List,
    beforeEnter: (to, from, next) => {
      next();
    }
  }
];

3)元件守衛

  元件內的導航守衛包含三個,如下所列,它們的引數都和beforeEach()的相同,也是to、from和next。

  (1)beforeRouteEnter():由於該元件在導航確認前被呼叫,因此元件還未被建立,從而訪問this無法得到元件例項。但next()函式能接收一個引數為元件例項的回撥,如下所示。

const List = {
  beforeRouteEnter: function(to, from, next) {
    next(vm => { 
      //vm是元件例項
    });
  }
};

  (2)beforeRouteUpdate():可通過this得到元件例項,當路由發生變化,但複用了一個元件時,會呼叫該守衛。例如一個路徑為/list/:page的路由(如下配置所示),在將/list/1導航至list/2時,由於渲染的都是List元件,因此會呼叫beforeRouteUpdate()守衛。

const routes = [
  {
    path: "/list/:page",
    component: List
  }
];

  (3)beforeRouteLeave():也可通過this得到元件例項,當路由改變,並且渲染的元件不同時,會呼叫該守衛。例如有兩條路徑/main和/list,分別對應Main和List元件,在List元件中聲明瞭beforeRouteLeave()守衛(如下所示),當從/list導航至/main時,會彈出確認框,判斷是否需要取消本次導航。

const List = {
  beforeRouteLeave: function(to, from, next) {
    const answer = window.confirm("是否離開當前頁?");
    answer ? next() : next(false);
  }
};

4)解析流程

  完整的導航解析流程如下所列。

(1)導航被觸發。

(2)在失活的元件裡呼叫beforeRouteLeave()守衛。

(3)呼叫全域性的beforeEach()守衛。

(4)在重用的元件裡呼叫beforeRouteUpdate()守衛。

(5)在路由配置裡呼叫beforeEnter()守衛。

(6)解析非同步路由元件。

(7)在被啟用的元件裡呼叫beforeRouteEnter()守衛。

(8)呼叫全域性的beforeResolve()守衛。

(9)導航被確認。

(10)呼叫全域性的afterEach()守衛。

(11)觸發DOM更新。

(12)用建立好的例項呼叫beforeRouteEnter()守衛中傳給next引數的回撥函式。

&n