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