SPA(single page application):單一頁面應用程序,只有一個完整的頁面;它在加載頁面時,不會加載整個頁面,而是隻更新某個指定的容器中內容。單頁面應用(SPA)的核心之一是:更新視圖而不從新請求頁面;vue-router在實現單頁面前端路由時,提供了兩種方式:Hash模式和History模式。html
隨着 ajax 的流行,異步數據請求交互運行在不刷新瀏覽器的狀況下進行。而異步交互體驗的更高級版本就是 SPA —— 單頁應用。單頁應用不只僅是在頁面交互是無刷新的,連頁面跳轉都是無刷新的,爲了實現單頁應用,因此就有了前端路由。相似於服務端路由,前端路由實現起來其實也很簡單,就是匹配不一樣的 url 路徑,進行解析,而後動態的渲染出區域 html 內容。可是這樣存在一個問題,就是 url 每次變化的時候,都會形成頁面的刷新。那解決問題的思路即是在改變url的狀況下,保證頁面的不刷新。在 2014 年以前,你們是經過 hash 來實現路由,url hash 就是相似於:前端
http://www.xxx.com/#/login
複製代碼
這種 #。後面 hash 值的變化,並不會致使瀏覽器向服務器發出請求,瀏覽器不發出請求,也就不會刷新頁面。另外每次 hash 值的變化,還會觸發hashchange 這個事件,經過這個事件咱們就能夠知道 hash 值發生了哪些變化。而後咱們即可以監聽hashchange來實現更新頁面部份內容的操做:vue
function matchAndUpdate () {
// todo 匹配 hash 作 dom 更新操做
}
window.addEventListener('hashchange', matchAndUpdate)
複製代碼
14年後,由於HTML5標準發佈。多了兩個 API,pushState 和 replaceState,經過這兩個 API 能夠改變 url 地址且不會發送請求。同時還有popstate事件。經過這些就能用另外一種方式來實現前端路由了,但原理都是跟 hash 實現相同的。用了HTML5的實現,單頁路由的url就不會多出一個#,變得更加美觀。但由於沒有 # 號,因此當用戶刷新頁面之類的操做時,瀏覽器仍是會給服務器發送請求。爲了不出現這種狀況,因此這個實現須要服務器的支持,須要把全部路由都重定向到根頁面。react
function matchAndUpdate () {
// todo 匹配路徑 作 dom 更新操做
}
window.addEventListener('popstate', matchAndUpdate)
複製代碼
咱們常常須要把某種模式匹配到的全部路由,全都映射到同個組件。例如,咱們有一個 User 組件,對於全部 ID 各不相同的用戶,都要使用這個組件來渲染。那麼,咱們能夠在 vue-router 的路由路徑中使用「動態路徑參數」(dynamic segment) 來達到這個效果ajax
const User = {
template: '<div>User</div>'
}
const router = new VueRouter({
routes: [
// 動態路徑參數 以冒號開頭
{ path: '/user/:id', component: User }
]
})
複製代碼
如今呢,像 /user/foo 和 /user/bar 都將映射到相同的路由。vue-router
一個「路徑參數」使用冒號 : 標記。當匹配到一個路由時,參數值會被設置到 this.$route.params,能夠在每一個組件內使用。因而,咱們能夠更新 User 的模板,輸出當前用戶的 ID:編程
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
複製代碼
你能夠在一個路由中設置多段『路徑參數』,對應的值都會設置到 $route.params 中。例如:瀏覽器
提醒一下,當使用路由參數時,例如從/user/foo導航到/user/bar,原來的組件實例會被複用。由於兩個路由都渲染同個組件,比起銷燬再建立,複用則顯得更加高效。不過,這也意味着組件的生命週期鉤子不會再被調用。複用組件時,想對路由參數的變化做出響應的話,你能夠簡單地 watch (監測變化) $route 對象bash
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() } } 複製代碼
實際生活中的應用界面,一般由多層嵌套的組件組合而成。一樣地,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
}
]
}
]
})
複製代碼
注意:在 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)
複製代碼
有時候想同時 (同級) 展現多個視圖,而不是嵌套展現,例如建立一個佈局,有 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
}
}]
}
複製代碼
重定向也是經過 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' }
]
})
複製代碼
總結:
當作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.beforeEach 相似,區別是在導航被確認以前,同時在全部組件內守衛和異步路由組件被解析以後,解析守衛就被調用
你能夠在路由配置上直接定義 beforeEnter 守衛:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
複製代碼
你能夠在路由組件內直接定義如下路由導航守衛:
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)
}
}
複製代碼
完整的導航解析流程