vue-router使用詳情
SPA(single page application):單一頁面應用程式,只有一個完整的頁面;它在載入頁面時,不會載入整個頁面,而是隻更新某個指定的容器中內容。單頁面應用(SPA)的核心之一是: 更新檢視而不重新請求頁面 ;vue-router在實現單頁面前端路由時,提供了兩種方式:Hash模式和History模式。
1、hash模式
隨著 ajax 的流行,非同步資料請求互動執行在不重新整理瀏覽器的情況下進行。而非同步互動體驗的更高階版本就是 SPA —— 單頁應用。單頁應用不僅僅是在頁面互動是無重新整理的,連頁面跳轉都是無重新整理的,為了實現單頁應用,所以就有了前端路由。類似於服務端路由,前端路由實現起來其實也很簡單,就是匹配不同的 url 路徑,進行解析,然後動態的渲染出區域 html 內容。但是這樣存在一個問題,就是 url 每次變化的時候,都會造成頁面的重新整理。那解決問題的思路便是在改變url的情況下,保證頁面的不重新整理。在 2014 年之前,大家是通過 hash 來實現路由,url hash 就是類似於:
http://www.xxx.com/#/login 複製程式碼
這種 #。後面 hash 值的變化,並不會導致瀏覽器向伺服器發出請求,瀏覽器不發出請求,也就不會重新整理頁面。另外每次 hash 值的變化,還會觸發hashchange 這個事件,通過這個事件我們就可以知道 hash 值發生了哪些變化。然後我們便可以監聽hashchange來實現更新頁面部分內容的操作:
function matchAndUpdate () { // todo 匹配 hash 做 dom 更新操作 } window.addEventListener('hashchange', matchAndUpdate) 複製程式碼
2、history 模式
14年後,因為HTML5標準釋出。多了兩個 API,pushState 和 replaceState,通過這兩個 API 可以改變 url 地址且不會發送請求。同時還有popstate事件。通過這些就能用另一種方式來實現前端路由了,但原理都是跟 hash 實現相同的。用了HTML5的實現,單頁路由的url就不會多出一個#,變得更加美觀。但因為沒有 # 號,所以當用戶重新整理頁面之類的操作時,瀏覽器還是會給伺服器傳送請求。為了避免出現這種情況,所以這個實現需要伺服器的支援,需要把所有路由都重定向到根頁面。
function matchAndUpdate () { // todo 匹配路徑 做 dom 更新操作 } window.addEventListener('popstate', matchAndUpdate) 複製程式碼
vue-router的使用
1、動態路由匹配
我們經常需要把某種模式匹配到的所有路由,全都對映到同個元件。例如,我們有一個 User 元件,對於所有 ID 各不相同的使用者,都要使用這個元件來渲染。那麼,我們可以在 vue-router 的路由路徑中使用“動態路徑引數”(dynamic segment) 來達到這個效果
const User = { template: '<div>User</div>' } const router = new VueRouter({ routes: [ // 動態路徑引數 以冒號開頭 { path: '/user/:id', component: User } ] }) 複製程式碼
現在呢,像 /user/foo 和 /user/bar 都將對映到相同的路由。
一個“路徑引數”使用冒號 : 標記。當匹配到一個路由時,引數值會被設定到 this.$route.params,可以在每個元件內使用。於是,我們可以更新 User 的模板,輸出當前使用者的 ID:
const User = { template: '<div>User {{ $route.params.id }}</div>' } 複製程式碼
你可以在一個路由中設定多段『路徑引數』,對應的值都會設定到 $route.params 中。例如:

提醒一下,當使用路由引數時,例如從/user/foo導航到/user/bar,原來的元件例項會被複用。因為兩個路由都渲染同個元件,比起銷燬再建立,複用則顯得更加高效。不過,這也意味著元件的生命週期鉤子不會再被呼叫。複用元件時,想對路由引數的變化作出響應的話,你可以簡單地 watch (監測變化) $route 物件
const User = { template: '...', watch: { '$route' (to, from) { // 對路由變化作出響應... } } } 複製程式碼
或者使用 2.2 中引入的 beforeRouteUpdate 守衛:
const User = { template: '...', beforeRouteUpdate (to, from, next) { // react to route changes... // don't forget to call next() } } 複製程式碼
2、巢狀路由
實際生活中的應用介面,通常由多層巢狀的元件組合而成。同樣地,URL 中各段動態路徑也按某種結構對應巢狀的各層元件,例如

藉助 vue-router,使用巢狀路由配置,就可以很簡單地表達這種關係
<div id="app"> <router-view></router-view> </div> 複製程式碼
const User = { template: '<div>User {{ $route.params.id }}</div>' } const router = new VueRouter({ routes: [ { path: '/user/:id', component: User } ] }) 複製程式碼
這裡的 <router-view>
是最頂層的出口,渲染最高階路由匹配到的元件。同樣地,一個被渲染元件同樣可以包含自己的巢狀 <router-view>
。例如,在 User 元件的模板新增一個 <router-view>
const User = { template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> ` } 複製程式碼
要在巢狀的出口中渲染元件,需要在 VueRouter 的引數中使用 children 配置:
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // 當 /user/:id/profile 匹配成功, // UserProfile 會被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile }, { // 當 /user/:id/posts 匹配成功 // UserPosts 會被渲染在 User 的 <router-view> 中 path: 'posts', component: UserPosts } ] } ] }) 複製程式碼
3、程式設計式導航
注意:在 Vue 例項內部,你可以通過 $router
訪問路由例項。因此你可以呼叫 this.$router.push
。

引數可以是一個字串路徑,或者一個描述地址的物件: // 字串 router.push('home') // 物件 router.push({ path: 'home' }) // 命名的路由 router.push({ name: 'user', params: { userId: 123 }}) // 帶查詢引數,變成 /register?plan=private router.push({ path: 'register', query: { plan: 'private' }}) const userId = '123' router.push({ name: 'user', params: { userId }}) // -> /user/123 router.push({ path: `/user/${userId}` }) // -> /user/123 // 這裡的 params 不生效 router.push({ path: '/user', params: { userId }}) // -> /user //router.go(n)這個方法的引數是一個整數,意思是在 history 記錄中向前或者後退多少步,類似 window.history.go(n) // 在瀏覽器記錄中前進一步,等同於 history.forward() router.go(1) // 後退一步記錄,等同於 history.back() router.go(-1) // 前進 3 步記錄 router.go(3) // 如果 history 記錄不夠用,那就默默地失敗唄 router.go(-100) router.go(100) 複製程式碼
4、命名檢視
有時候想同時 (同級) 展示多個檢視,而不是巢狀展示,例如建立一個佈局,有 sidebar (側導航) 和 main (主內容) 兩個檢視,這個時候命名檢視就派上用場了。你可以在介面中擁有多個單獨命名的檢視,而不是隻有一個單獨的出口。如果 router-view 沒有設定名字,那麼預設為 default。
//name對應的是元件名字 <router-view class="view one"></router-view> <router-view class="view two" name="a"></router-view> <router-view class="view three" name="b"></router-view> 複製程式碼
一個檢視使用一個元件渲染,因此對於同個路由,多個檢視就需要多個元件。確保正確使用 components 配置 (帶上 s):
const router = new VueRouter({ routes: [ { path: '/', components: { default: Foo,//Foo是元件名字 a: Bar,//Bar是元件名字 b: Baz//Baz是元件名字 } } ] }) 複製程式碼
巢狀命名檢視
UserSettings
元件的 <template>
部分應該是類似下面的這段程式碼:
<!-- UserSettings.vue --> <div> <h1>User Settings</h1> <NavBar/> <router-view/> <router-view name="helper"/> </div> 複製程式碼
然後你可以用這個路由配置完成該佈局:
{ path: '/settings', // 你也可以在頂級路由就配置命名檢視 component: UserSettings, children: [{ path: 'emails', component: UserEmailsSubscriptions }, { path: 'profile', components: { default: UserProfile, helper: UserProfilePreview } }] } 複製程式碼
5、重定向與別名
重定向也是通過 routes 配置來完成,下面例子是從 /a 重定向到 /b:
const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] }) 複製程式碼
重定向的目標也可以是一個命名的路由:
const router = new VueRouter({ routes: [ { path: '/a', redirect: { name: 'foo' }} ] }) 複製程式碼
甚至是一個方法,動態返回重定向目標:
const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { // 方法接收 目標路由 作為引數 // return 重定向的 字串路徑/路徑物件 }} ] }) 複製程式碼
別名:/a 的別名是 /b,意味著,當用戶訪問 /b 時,URL 會保持為 /b,但是路由匹配則為 /a,就像使用者訪問 /a 一樣。
const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] }) 複製程式碼
總結:
- 『重定向』的意思是:當用戶訪問/a時,URL將會被替換成/b,然後匹配路由為/b。 (換藥換湯)
- 別名的意思是:/a的別名是/b,意味著,當用戶訪問/b時,URL會保持為/b,但是路由匹配則為/a,就像使用者訪問/a 一樣。 (換湯不換藥)
6、導航守衛
當做Vue-cli專案的時候感覺在路由跳轉前做一些驗證,比如登入驗證,是網站中的普遍需求。對此,vue-router 提供的 beforeEach可以方便地實現全域性導航守衛(navigation-guards)。
如何設定一個全域性守衛
你可以使用 router.beforeEach 註冊一個全域性前置守衛:就是在你router配置的下方註冊
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... }) 複製程式碼
當一個導航觸發時,全域性前置守衛按照建立順序呼叫。守衛是非同步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中。
每個守衛方法接收三個引數:
-
to: Route:即將要進入的目標 路由物件
-
from: Route:當前導航正要離開的路由
-
next: Function:一定要呼叫該方法來 resolve 這個鉤子。執行效果依賴 next 方法的呼叫引數。
-
next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
-
next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了 (可能是使用者手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。
-
next('/') 或者 next({ path: '/' }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。你可以向 next 傳遞任意位置物件,且允許設定諸如 replace: true、name: 'home' 之類的選項以及任何用在 router-link 的 to prop 或 router.push 中的選項。
-
next(error): (2.4.0+) 如果傳入 next 的引數是一個 Error 例項,則導航會被終止且該錯誤會被傳遞給 router.onError() 註冊過的回撥。
確保要呼叫 next 方法,否則鉤子就不會被 resolved。舉個例子:
const router = new VueRouter({ ... }) //這是路由配置,我就不多說了 const whiteList = ['/error', '/register/regindex', '/register/userauthent','/register/submit'] // 路由白名單 vueRouter.beforeEach(function(to,from,next){ console.log("進入守衛"); if (userInfo.user_id>0){ console.log("登入成功"); next();//記得當所有程式執行完畢後要進行next(),不然是無法繼續進行的; }else{ console.log("登入失敗"); getUserInfo.then(res => { if(res){ if (res.user_id){ if (res.status == 4) { //賬號凍結 next({ path: '/error', replace: true, query: { noGoBack: true } }) } if (res.status == 3) { //認證稽核中 next({ path: '/register/submit', replace: true, query: { noGoBack: true } }) } if (res.status != 1 && res.status != 3) { if (!res.mobile ) { next({ path: '/register/regindex', replace: true, query: { noGoBack: true }}) } else { //繫結完手機號了 next({ path: '/register/userauthent', replace: true, query: { noGoBack: true } }) } } next();//記得當所有程式執行完畢後要進行next(),不然是無法繼續進行的; }else{ if (whiteList.indexOf(to.path) !== -1) { // 在免登入白名單,直接進入 next();//記得當所有程式執行完畢後要進行next(),不然是無法繼續進行的; }else{ next({ path: '/register/regindex', replace: true, query: { noGoBack: true }}) } } }else{ } } }).catch(()=>{ //跳轉失敗頁面 next({ path: '/error', replace: true, query: { noGoBack: true }}) }) } }); export default router 複製程式碼
最後和大家說下如果白名單太多或專案更大時,我們需要把白名單換為vue-router路由元資訊:
直接在路由配置的時候,給每個路由新增一個自定義的meta物件,在meta物件中可以設定一些狀態,來進行一些操作。用它來做登入校驗再合適不過了
{ path: '/actile', name: 'Actile', component: Actile, meta: { login_require: false }, }, { path: '/goodslist', name: 'goodslist', component: Goodslist, meta: { login_require: true }, children:[ { path: 'online', component: GoodslistOnline } ] } 複製程式碼
這裡我們只需要判斷item下面的meta物件中的login_require是不是true,就可以做一些限制了
router.beforeEach((to, from, next) => { if (to.matched.some(function (item) { return item.meta.login_require })) { next('/login') } else next() }) 複製程式碼
全域性解析守衛(router.beforeResolve)
你可以用 router.beforeResolve 註冊一個全域性守衛。這和 router.beforeEach 類似,區別是在導航被確認之前, 同時在所有元件內守衛和非同步路由元件被解析之後 ,解析守衛就被呼叫
路由獨享的守衛
你可以在路由配置上直接定義 beforeEnter 守衛:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] }) 複製程式碼
元件內的守衛
你可以在路由元件內直接定義以下路由導航守衛:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染該元件的對應路由被 confirm 前呼叫 // 不!能!獲取元件例項 `this` // 因為當守衛執行前,元件例項還沒被建立 }, beforeRouteUpdate (to, from, next) { // 在當前路由改變,但是該元件被複用時呼叫 // 舉例來說,對於一個帶有動態引數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候, // 由於會渲染同樣的 Foo 元件,因此元件例項會被複用。而這個鉤子就會在這個情況下被呼叫。 // 可以訪問元件例項 `this` }, beforeRouteLeave (to, from, next) { // 導航離開該元件的對應路由時呼叫 // 可以訪問元件例項 `this` } } 複製程式碼
beforeRouteEnter 守衛 不能 訪問 this,因為守衛在導航確認前被呼叫,因此即將登場的新元件還沒被建立。
不過,你可以通過傳一個回撥給 next來訪問元件例項。在導航被確認的時候執行回撥,並且把元件例項作為回撥方法的引數。
beforeRouteEnter (to, from, next) { next(vm => { // 通過 `vm` 訪問元件例項 }) } 複製程式碼
這個離開守衛通常用來禁止使用者在還未儲存修改前突然離開。該導航可以通過 next(false) 來取消。
beforeRouteLeave (to, from , next) { const answer = window.confirm('Do you really want to leave? you have unsaved changes!') if (answer) { next() } else { next(false) } } 複製程式碼
完整的導航解析流程
