解析Vue-router相關乾貨及工作原理
本文主要介紹了vue-router相關基礎知識及單頁面應用的工作原理,寫的十分的全面細緻,具有一定的參考價值,對此有需要的朋友可以參考學習下。如有不足之處,歡迎批評指正。
單頁面工作原理是通過瀏覽器URL的#後面的hash變化就會引起頁面變化的特性來把頁面分成不同的小模組,然後通過修改hash來讓頁面展示我們想讓看到的內容。
那麼為什麼hash的不同,為什麼會影響頁面的展示呢?瀏覽器在這裡面做了什麼內容。以前#後面的內容一般會做錨點,但是會定位到一個頁面的某個位置,這個是怎麼做到的呢,和我們現在的路由有什麼不同。(我能想到一個路由的展示就會把其他路由隱藏,是這樣的嗎)後面會看一看寫一下這個疑惑,現在最重要的是先把基本概念弄熟。
當你要把 vue-router 新增進來,我們需要做的是,將元件(components)對映到路由(routes),然後告訴 vue-router 在哪裡渲染它們 起步
//*** router-link 告訴瀏覽器去哪個路由 //*** router-view 告訴路由在哪裡展示內容 <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 元件來導航. --> <!-- 通過傳入 `to` 屬性指定連結. --> <!-- <router-link> 預設會被渲染成一個 `<a>` 標籤 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的元件將渲染在這裡 --> <router-view></router-view> </div> // 1. 定義(路由)元件。 // 可以從其他檔案 import 進來 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定義路由 // 每個路由應該對映一個元件。 其中"component" 可以是 // 通過 Vue.extend() 建立的元件構造器, // 或者,只是一個元件配置物件。 // 我們晚點再討論巢狀路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ]//歡迎加入前端全棧開發交流圈一起學習交流:864305860 // 3. 建立 router 例項,然後傳 `routes` 配置 // 你還可以傳別的配置引數, 不過先這麼簡單著吧。 const router = new VueRouter({ routes // (縮寫)相當於 routes: routes }) // 4. 建立和掛載根例項。 // 記得要通過 router 配置引數注入路由, // 從而讓整個應用都有路由功能 const app = new Vue({ router }).$mount('#app') // 現在,應用已經啟動了!
動態路由匹配
相當於同一個元件,因為引數不同展示不同的元件內容,其實就是在 vue-router 的路由路徑中使用『動態路徑引數』
const router = new VueRouter({
routes: [
// 動態路徑引數 以冒號開頭
{ path: '/user/:id', component: User }
]
})
那麼我們進入uesr/001 和 user/002 其實是進入的同一個路由,可以根據引數的不同在內容頁展示不同的內容。一般適用場景:列表,許可權控制 定義的時候用: 表示是動態路由
使用{{ $route.params.id }}
來拿到本路由裡面引數的內容
當使用路由引數時,例如從/user/foo
/user/bar
,原來的元件例項會被複用。因為兩個路由都渲染同個元件,比起銷燬再建立,複用則顯得更加高效。不過,這也意味著元件的生命週期鉤子不會再被呼叫。
複用元件時,想對路由引數的變化作出響應的話,你可以簡單地 watch(監測變化)$route
物件
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 對路由變化作出響應...
}//歡迎加入前端全棧開發交流圈一起學習交流:864305860
}
}
有時候,同一個路徑可以匹配多個路由,此時,匹配的優先順序就按照路由的定義順序:誰先定義的,誰的優先順序就最高。
巢狀路由
在路由裡面巢狀一個路由
//路由裡面也會出現 <router-view> 這是巢狀路由展示內容的地方
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
//定義路由的時候在 加children 子路由屬性
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User,
children: [
{
// 當 /user/:id/profile 匹配成功,
// UserProfile 會被渲染在 User 的 <router-view> 中
path: 'profile',
component: UserProfile
},//歡迎加入前端全棧開發交流圈一起學習交流:864305860
{
// 當 /user/:id/posts 匹配成功
// UserPosts 會被渲染在 User 的 <router-view> 中
path: 'posts',
component: UserPosts
}
]
}
]
})
設定空路由,在沒有指定路由的時候就會展示空路由內容
const router = new VueRouter({
routes: [
{
path: '/user/:id', component: User,
children: [
// 當 /user/:id 匹配成功,
// UserHome 會被渲染在 User 的 <router-view> 中
{ path: '', component: UserHome },
]//歡迎加入前端全棧開發交流圈一起學習交流:864305860
}
]
})
程式設計式導航
宣告式:<router-link :to="..."> 程式設計式:router.push(...) 可以想象程式設計式 push 可以理解為向瀏覽器歷史裡面push一個新的hash,導致路由發生變化 router.replace() 修改路由但是不存在歷史裡面 router.go(n) 有點像JS的window.history.go(n) 命名路由 就是給每一個路由定義一個名字。
命名檢視
有時候想同時(同級)展示多個檢視,而不是巢狀展示,例如建立一個佈局,有 sidebar(側導航) 和 main(主內容) 兩個檢視,這個時候命名檢視就派上用場了。你可以在介面中擁有多個單獨命名的檢視,而不是隻有一個單獨的出口。如果 router-view 沒有設定名字,那麼預設為 default。
<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,
a: Bar,
b: Baz
}
}
]
})//歡迎加入前端全棧開發交流圈一起學習交流:864305860
重定向和別名
重定向也是通過 routes 配置來完成,下面例子是從 /a 重定向到 /b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
一般首頁的時候可以重定向到其他的地方 重定向的目標也可以是一個命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]//歡迎加入前端全棧開發交流圈一起學習交流:864305860
})
甚至是一個方法,動態返回重定向目標:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目標路由 作為引數
// return 重定向的 字串路徑/路徑物件
}}
]
})
『重定向』的意思是,當用戶訪問 /a時,URL 將會被替換成 /b,然後匹配路由為 /b,那麼『別名』又是什麼呢? /a 的別名是 /b,意味著,當用戶訪問 /b 時,URL 會保持為 /b,但是路由匹配則為 /a,就像使用者訪問 /a 一樣。 上面對應的路由配置為:
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]//歡迎加入前端全棧開發交流圈一起學習交流:864305860
})
『別名』的功能讓你可以自由地將 UI 結構對映到任意的 URL,而不是受限於配置的巢狀路由結構。
HTML5 History 模式
ue-router 預設 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,於是當 URL 改變時,頁面不會重新載入。 如果不想要很醜的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉而無須重新載入頁面。
const router = new VueRouter({
mode: 'history',
routes: [...]
})//歡迎加入前端全棧開發交流圈一起學習交流:864305860
當你使用 history 模式時,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!
不過這種模式要玩好,還需要後臺配置支援。因為我們的應用是個單頁客戶端應用,如果後臺沒有正確的配置,當用戶在瀏覽器直接訪問 http://oursite.com/user/id 就會返回 404,這就不好看了。
所以呢,你要在服務端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態資源,則應該返回同一個 index.html 頁面,這個頁面就是你 app 依賴的頁面。
給個警告,因為這麼做以後,你的伺服器就不再返回 404 錯誤頁面,因為對於所有路徑都會返回 index.html 檔案。為了避免這種情況,你應該在 Vue 應用裡面覆蓋所有的路由情況,然後在給出一個 404 頁面。
const router = new VueRouter({
mode: 'history',
routes: [//歡迎加入前端全棧開發交流圈一起學習交流:864305860
{ path: '*', component: NotFoundComponent }
]
})
或者,如果你使用 Node.js 伺服器,你可以用服務端路由匹配到來的 URL,並在沒有匹配到路由的時候返回 404,以實現回退。
導航守衛
我的理解 就是元件或者全域性級別的 元件的鉤子函式 正如其名,vue-router 提供的導航守衛主要用來通過跳轉或取消的方式守衛導航。有多種機會植入路由導航過程中:全域性的, 單個路由獨享的, 或者元件級的。 記住引數或查詢的改變並不會觸發進入/離開的導航守衛。你可以通過觀察 $route 物件來應對這些變化,或使用 beforeRouteUpdate 的元件內守衛。
全域性守衛
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
每個守衛方法接收三個引數: 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。
全域性後置鉤子
你也可以註冊全域性後置鉤子,然而和守衛不同的是,這些鉤子不會接受 next 函式也不會改變導航本身:
router.afterEach((to, from) => {
// ...
})\
路由獨享的守衛
你可以在路由配置上直接定義 beforeEnter 守衛:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...//歡迎加入前端全棧開發交流圈一起學習交流:864305860
}
}
]
})
這些守衛與全域性前置守衛的方法引數是一樣的。
元件內的守衛
最後,你可以在路由元件內直接定義以下路由導航守衛:
beforeRouteEnter
beforeRouteUpdate (2.2 新增)
beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染該元件的對應路由被 confirm 前呼叫
// 不!能!獲取元件例項 `this`
// 因為當守衛執行前,元件例項還沒被建立
},//歡迎加入前端全棧開發交流圈一起學習交流:864305860
beforeRouteUpdate (to, from, next) {
// 在當前路由改變,但是該元件被複用時呼叫
// 舉例來說,對於一個帶有動態引數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由於會渲染同樣的 Foo 元件,因此元件例項會被複用。而這個鉤子就會在這個情況下被呼叫。
// 可以訪問元件例項 `this`
},
beforeRouteLeave (to, from, next) {
// 導航離開該元件的對應路由時呼叫
// 可以訪問元件例項 `this`
}
}
beforeRouteEnter 守衛 不能 訪問 this,因為守衛在導航確認前被呼叫,因此即將登場的新元件還沒被建立。 完整的導航解析流程
導航被觸發。
在失活的元件裡呼叫離開守衛。
呼叫全域性的 beforeEach 守衛。
在重用的元件裡呼叫 beforeRouteUpdate 守衛 (2.2+)。
在路由配置裡呼叫 beforeEnter。
解析非同步路由元件。
在被啟用的元件裡呼叫 beforeRouteEnter。
呼叫全域性的 beforeResolve 守衛 (2.5+)。
導航被確認。
呼叫全域性的 afterEach 鉤子。
觸發 DOM 更新。
用建立好的例項呼叫 beforeRouteEnter 守衛中傳給 next 的回撥函式。
路由元資訊
我的理解就是 他可以把路由的父路徑都列舉出來,完成一些任務,比如登入,user 元件需要登入,那麼user下面的foo元件也需要,那麼可以通過這個屬性 來檢測這個路由線上 的一些狀態。 定義路由的時候可以配置 meta 欄位:
const router = new VueRouter({
routes: [
{//歡迎加入前端全棧開發交流圈一起學習交流:864305860
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
首先,我們稱呼 routes 配置中的每個路由物件為 路由記錄。路由記錄可以是巢狀的,因此,當一個路由匹配成功後,他可能匹配多個路由記錄 例如,根據上面的路由配置,/foo/bar 這個 URL 將會匹配父路由記錄以及子路由記錄。 一個路由匹配到的所有路由記錄會暴露為 $route 物件(還有在導航守衛中的路由物件)的 $route.matched 陣列。因此,我們需要遍歷 $route.matched 來檢查路由記錄中的 meta 欄位。 下面例子展示在全域性導航守衛中檢查元欄位:
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({//歡迎加入前端全棧開發交流圈一起學習交流:864305860
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 確保一定要呼叫 next()
}//歡迎加入前端全棧開發交流圈一起學習交流:864305860
})
資料獲取
我的理解就是在哪裡獲取資料,可以再元件裡面,也可以在元件的守衛裡面,也就是元件的生命週期裡面。 有時候,進入某個路由後,需要從伺服器獲取資料。例如,在渲染使用者資訊時,你需要從伺服器獲取使用者的資料。我們可以通過兩種方式來實現: 導航完成之後獲取:先完成導航,然後在接下來的元件生命週期鉤子中獲取資料。在資料獲取期間顯示『載入中』之類的指示。 導航完成之前獲取:導航完成前,在路由進入的守衛中獲取資料,在資料獲取成功後執行導航。 從技術角度講,兩種方式都不錯 —— 就看你想要的使用者體驗是哪種。
導航完成後獲取資料
當你使用這種方式時,我們會馬上導航和渲染元件,然後在元件的 created 鉤子中獲取資料。這讓我們有機會在資料獲取期間展示一個 loading 狀態,還可以在不同檢視間展示不同的 loading 狀態。 假設我們有一個 Post 元件,需要基於 $route.params.id 獲取文章資料:
<template>
<div class="post">
<div class="loading" v-if="loading">
Loading...
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>//歡迎加入前端全棧開發交流圈一起學習交流:864305860
</div>
</template>
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 元件建立完後獲取資料,
// 此時 data 已經被 observed 了
this.fetchData()
},
watch: {
// 如果路由有變化,會再次執行該方法
'$route': 'fetchData'
},//歡迎加入前端全棧開發交流圈一起學習交流:864305860
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {//歡迎加入前端全棧開發交流圈一起學習交流:864305860
this.post = post
}
})
}
}
}
在導航完成前獲取資料
通過這種方式,我們在導航轉入新的路由前獲取資料。我們可以在接下來的元件的 beforeRouteEnter 守衛中獲取資料,當資料獲取成功後只調用 next 方法。
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})//歡迎加入前端全棧開發交流圈一起學習交流:864305860
},
// 路由改變前,元件就已經渲染完了
// 邏輯稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()//歡迎加入前端全棧開發交流圈一起學習交流:864305860
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}//歡迎加入前端全棧開發交流圈一起學習交流:864305860
}
}
}
在為後面的檢視獲取資料時,使用者會停留在當前的介面,因此建議在資料獲取期間,顯示一些進度條或者別的指示。如果資料獲取失敗,同樣有必要展示一些全域性的錯誤提醒。
結語
感謝您的觀看,如有不足之處,歡迎批評指正。