vue-router相關(guān)基礎(chǔ)知識及工作原理
前言
今天面試被問到 vue的動(dòng)態(tài)路由,我竟然沒有回答上來,感覺不是什么難得問題。好久沒有看vue-router的文檔,很多用的東西和概念沒有對上?;貋硪豢词裁词莿?dòng)態(tài)路由就傻眼了??磥碛斜匾裿ue -router相關(guān)知識總結(jié)一下,好丟人的感覺。
單頁面應(yīng)用的工作原理
我理解的單頁面工作原理是通過瀏覽器URL的#后面的hash變化就會(huì)引起頁面變化的特性來把頁面分成不同的小模塊,然后通過修改hash來讓頁面展示我們想讓看到的內(nèi)容。
那么為什么hash的不同,為什么會(huì)影響頁面的展示呢?瀏覽器在這里面做了什么內(nèi)容。以前#后面的內(nèi)容一般會(huì)做錨點(diǎn),但是會(huì)定位到一個(gè)頁面的某個(gè)位置,這個(gè)是怎么做到的呢,和我們現(xiàn)在的路由有什么不同。(我能想到一個(gè)路由的展示就會(huì)把其他路由隱藏,是這樣的嗎)后面會(huì)看一看寫一下這個(gè)疑惑,現(xiàn)在最重要的是先把基本概念弄熟。
正文
當(dāng)你要把 vue-router 添加進(jìn)來,我們需要做的是,將組件(components)映射到路由(routes),然后告訴 vue-router 在哪里渲染它們
起步
//*** router-link 告訴瀏覽器去哪個(gè)路由 //*** router-view 告訴路由在哪里展示內(nèi)容 <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 組件來導(dǎo)航. --> <!-- 通過傳入 `to` 屬性指定鏈接. --> <!-- <router-link> 默認(rèn)會(huì)被渲染成一個(gè) `<a>` 標(biāo)簽 --> <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 進(jìn)來 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定義路由 // 每個(gè)路由應(yīng)該映射一個(gè)組件。 其中"component" 可以是 // 通過 Vue.extend() 創(chuàng)建的組件構(gòu)造器, // 或者,只是一個(gè)組件配置對象。 // 我們晚點(diǎn)再討論嵌套路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 創(chuàng)建 router 實(shí)例,然后傳 `routes` 配置 // 你還可以傳別的配置參數(shù), 不過先這么簡單著吧。 const router = new VueRouter({ routes // (縮寫)相當(dāng)于 routes: routes }) // 4. 創(chuàng)建和掛載根實(shí)例。 // 記得要通過 router 配置參數(shù)注入路由, // 從而讓整個(gè)應(yīng)用都有路由功能 const app = new Vue({ router }).$mount('#app') // 現(xiàn)在,應(yīng)用已經(jīng)啟動(dòng)了!
動(dòng)態(tài)路由匹配
相當(dāng)于同一個(gè)組件,因?yàn)閰?shù)不同展示不同的組件內(nèi)容,其實(shí)就是在 vue-router 的路由路徑中使用『動(dòng)態(tài)路徑參數(shù)』
const router = new VueRouter({ routes: [ // 動(dòng)態(tài)路徑參數(shù) 以冒號開頭 { path: '/user/:id', component: User } ] })
那么我們進(jìn)入uesr/001
和 user/002
其實(shí)是進(jìn)入的同一個(gè)路由,可以根據(jù)參數(shù)的不同在內(nèi)容頁展示不同的內(nèi)容。一般適用場景:列表,權(quán)限控制
定義的時(shí)候用: 表示是動(dòng)態(tài)路由
使用 {{ $route.params.id }}
來拿到本路由里面參數(shù)的內(nèi)容
當(dāng)使用路由參數(shù)時(shí),例如從 /user/foo 導(dǎo)航到 /user/bar,原來的組件實(shí)例會(huì)被復(fù)用。因?yàn)閮蓚€(gè)路由都渲染同個(gè)組件,比起銷毀再創(chuàng)建,復(fù)用則顯得更加高效。不過,這也意味著組件的生命周期鉤子不會(huì)再被調(diào)用。
復(fù)用組件時(shí),想對路由參數(shù)的變化作出響應(yīng)的話,你可以簡單地 watch(監(jiān)測變化) $route 對象
const User = { template: '...', watch: { '$route' (to, from) { // 對路由變化作出響應(yīng)... } } }
有時(shí)候,同一個(gè)路徑可以匹配多個(gè)路由,此時(shí),匹配的優(yōu)先級就按照路由的定義順序:誰先定義的,誰的優(yōu)先級就最高。
嵌套路由
在路由里面嵌套一個(gè)路由
//路由里面也會(huì)出現(xiàn) <router-view> 這是嵌套路由展示內(nèi)容的地方 const User = { template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> ` } //定義路由的時(shí)候在 加children 子路由屬性 const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ { // 當(dāng) /user/:id/profile 匹配成功, // UserProfile 會(huì)被渲染在 User 的 <router-view> 中 path: 'profile', component: UserProfile }, { // 當(dāng) /user/:id/posts 匹配成功 // UserPosts 會(huì)被渲染在 User 的 <router-view> 中 path: 'posts', component: UserPosts } ] } ] })
設(shè)置空路由,在沒有指定路由的時(shí)候就會(huì)展示空路由內(nèi)容
const router = new VueRouter({ routes: [ { path: '/user/:id', component: User, children: [ // 當(dāng) /user/:id 匹配成功, // UserHome 會(huì)被渲染在 User 的 <router-view> 中 { path: '', component: UserHome }, ] } ] })
編程式導(dǎo)航
聲明式:<router-link :to="...">
編程式:router.push(...)
可以想象編程式 push 可以理解為向?yàn)g覽器歷史里面push一個(gè)新的hash,導(dǎo)致路由發(fā)生變化
router.replace() 修改路由但是不存在歷史里面
router.go(n) 有點(diǎn)像JS的window.history.go(n)
命名路由 就是給每一個(gè)路由定義一個(gè)名字。
命名視圖
有時(shí)候想同時(shí)(同級)展示多個(gè)視圖,而不是嵌套展示,例如創(chuàng)建一個(gè)布局,有 sidebar(側(cè)導(dǎo)航) 和 main(主內(nèi)容) 兩個(gè)視圖,這個(gè)時(shí)候命名視圖就派上用場了。你可以在界面中擁有多個(gè)單獨(dú)命名的視圖,而不是只有一個(gè)單獨(dú)的出口。如果 router-view 沒有設(shè)置名字,那么默認(rèn)為 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>
一個(gè)視圖使用一個(gè)組件渲染,因此對于同個(gè)路由,多個(gè)視圖就需要多個(gè)組件。確保正確使用 components 配置(帶上 s):
const router = new VueRouter({ routes: [ { path: '/', components: { default: Foo, a: Bar, b: Baz } } ] })
重定向和別名
重定向也是通過 routes 配置來完成,下面例子是從 /a 重定向到 /b:
const router = new VueRouter({ routes: [ { path: '/a', redirect: '/b' } ] })
一般首頁的時(shí)候可以重定向到其他的地方
重定向的目標(biāo)也可以是一個(gè)命名的路由:
const router = new VueRouter({ routes: [ { path: '/a', redirect: { name: 'foo' }} ] })
甚至是一個(gè)方法,動(dòng)態(tài)返回重定向目標(biāo):
const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { // 方法接收 目標(biāo)路由 作為參數(shù) // return 重定向的 字符串路徑/路徑對象 }} ] })
『重定向』的意思是,當(dāng)用戶訪問 /a時(shí),URL 將會(huì)被替換成 /b,然后匹配路由為 /b,那么『別名』又是什么呢?
/a 的別名是 /b,意味著,當(dāng)用戶訪問 /b 時(shí),URL 會(huì)保持為 /b,但是路由匹配則為 /a,就像用戶訪問 /a 一樣。
上面對應(yīng)的路由配置為:
const router = new VueRouter({ routes: [ { path: '/a', component: A, alias: '/b' } ] })
『別名』的功能讓你可以自由地將 UI 結(jié)構(gòu)映射到任意的 URL,而不是受限于配置的嵌套路由結(jié)構(gòu)。
HTML5 History 模式
ue-router 默認(rèn) hash 模式 —— 使用 URL 的 hash 來模擬一個(gè)完整的 URL,于是當(dāng) URL 改變時(shí),頁面不會(huì)重新加載。
如果不想要很丑的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉(zhuǎn)而無須重新加載頁面。
const router = new VueRouter({ mode: 'history', routes: [...] })
當(dāng)你使用 history 模式時(shí),URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!
不過這種模式要玩好,還需要后臺(tái)配置支持。因?yàn)槲覀兊膽?yīng)用是個(gè)單頁客戶端應(yīng)用,如果后臺(tái)沒有正確的配置,當(dāng)用戶在瀏覽器直接訪問 http://oursite.com/user/id 就會(huì)返回 404,這就不好看了。
所以呢,你要在服務(wù)端增加一個(gè)覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態(tài)資源,則應(yīng)該返回同一個(gè) index.html 頁面,這個(gè)頁面就是你 app 依賴的頁面。
給個(gè)警告,因?yàn)檫@么做以后,你的服務(wù)器就不再返回 404 錯(cuò)誤頁面,因?yàn)閷τ谒新窂蕉紩?huì)返回 index.html 文件。為了避免這種情況,你應(yīng)該在 Vue 應(yīng)用里面覆蓋所有的路由情況,然后在給出一個(gè) 404 頁面。
const router = new VueRouter({ mode: 'history', routes: [ { path: '*', component: NotFoundComponent } ] })
或者,如果你使用 Node.js 服務(wù)器,你可以用服務(wù)端路由匹配到來的 URL,并在沒有匹配到路由的時(shí)候返回 404,以實(shí)現(xiàn)回退。
導(dǎo)航守衛(wèi)
我的理解 就是組件或者全局級別的 組件的鉤子函數(shù)
正如其名,vue-router 提供的導(dǎo)航守衛(wèi)主要用來通過跳轉(zhuǎn)或取消的方式守衛(wèi)導(dǎo)航。有多種機(jī)會(huì)植入路由導(dǎo)航過程中:全局的, 單個(gè)路由獨(dú)享的, 或者組件級的。
記住參數(shù)或查詢的改變并不會(huì)觸發(fā)進(jìn)入/離開的導(dǎo)航守衛(wèi)。你可以通過觀察 $route 對象來應(yīng)對這些變化,或使用 beforeRouteUpdate 的組件內(nèi)守衛(wèi)。
全局守衛(wèi)
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
每個(gè)守衛(wèi)方法接收三個(gè)參數(shù):
to: Route: 即將要進(jìn)入的目標(biāo) 路由對象
from: Route: 當(dāng)前導(dǎo)航正要離開的路由
next: Function: 一定要調(diào)用該方法來 resolve 這個(gè)鉤子。執(zhí)行效果依賴 next 方法的調(diào)用參數(shù)。
next(): 進(jìn)行管道中的下一個(gè)鉤子。如果全部鉤子執(zhí)行完了,則導(dǎo)航的狀態(tài)就是 confirmed (確認(rèn)的)。
next(false): 中斷當(dāng)前的導(dǎo)航。如果瀏覽器的 URL 改變了(可能是用戶手動(dòng)或者瀏覽器后退按鈕),那么 URL 地址會(huì)重置到 from 路由對應(yīng)的地址。
next(‘/') 或者 next({ path: ‘/' }): 跳轉(zhuǎn)到一個(gè)不同的地址。當(dāng)前的導(dǎo)航被中斷,然后進(jìn)行一個(gè)新的導(dǎo)航。你可以向 next 傳遞任意位置對象,且允許設(shè)置諸如 replace: true、name: ‘home' 之類的選項(xiàng)以及任何用在 router-link 的 to prop 或 router.push 中的選項(xiàng)。
next(error): (2.4.0+) 如果傳入 next 的參數(shù)是一個(gè) Error 實(shí)例,則導(dǎo)航會(huì)被終止且該錯(cuò)誤會(huì)被傳遞給 router.onError() 注冊過的回調(diào)。
確保要調(diào)用 next 方法,否則鉤子就不會(huì)被 resolved。
全局后置鉤子
你也可以注冊全局后置鉤子,然而和守衛(wèi)不同的是,這些鉤子不會(huì)接受 next 函數(shù)也不會(huì)改變導(dǎo)航本身:
router.afterEach((to, from) => { // ... })
路由獨(dú)享的守衛(wèi)
你可以在路由配置上直接定義 beforeEnter 守衛(wèi):
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // ... } } ] })
這些守衛(wèi)與全局前置守衛(wèi)的方法參數(shù)是一樣的。
組件內(nèi)的守衛(wèi)
最后,你可以在路由組件內(nèi)直接定義以下路由導(dǎo)航守衛(wèi):
beforeRouteEnter beforeRouteUpdate (2.2 新增) beforeRouteLeave const Foo = { template: `...`, beforeRouteEnter (to, from, next) { // 在渲染該組件的對應(yīng)路由被 confirm 前調(diào)用 // 不!能!獲取組件實(shí)例 `this` // 因?yàn)楫?dāng)守衛(wèi)執(zhí)行前,組件實(shí)例還沒被創(chuàng)建 }, beforeRouteUpdate (to, from, next) { // 在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用 // 舉例來說,對于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時(shí)候, // 由于會(huì)渲染同樣的 Foo 組件,因此組件實(shí)例會(huì)被復(fù)用。而這個(gè)鉤子就會(huì)在這個(gè)情況下被調(diào)用。 // 可以訪問組件實(shí)例 `this` }, beforeRouteLeave (to, from, next) { // 導(dǎo)航離開該組件的對應(yīng)路由時(shí)調(diào)用 // 可以訪問組件實(shí)例 `this` } }
beforeRouteEnter 守衛(wèi) 不能 訪問 this,因?yàn)槭匦l(wèi)在導(dǎo)航確認(rèn)前被調(diào)用,因此即將登場的新組件還沒被創(chuàng)建。
完整的導(dǎo)航解析流程
導(dǎo)航被觸發(fā)。
在失活的組件里調(diào)用離開守衛(wèi)。
調(diào)用全局的 beforeEach 守衛(wèi)。
在重用的組件里調(diào)用 beforeRouteUpdate 守衛(wèi) (2.2+)。
在路由配置里調(diào)用 beforeEnter。
解析異步路由組件。
在被激活的組件里調(diào)用 beforeRouteEnter。
調(diào)用全局的 beforeResolve 守衛(wèi) (2.5+)。
導(dǎo)航被確認(rèn)。
調(diào)用全局的 afterEach 鉤子。
觸發(fā) DOM 更新。
用創(chuàng)建好的實(shí)例調(diào)用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調(diào)函數(shù)。
路由元信息
我的理解就是 他可以把路由的父路徑都列舉出來,完成一些任務(wù),比如登錄,user 組件需要登錄,那么user下面的foo組件也需要,那么可以通過這個(gè)屬性 來檢測這個(gè)路由線上 的一些狀態(tài)。
定義路由的時(shí)候可以配置 meta 字段:
const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, children: [ { path: 'bar', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ] })
首先,我們稱呼 routes 配置中的每個(gè)路由對象為 路由記錄。路由記錄可以是嵌套的,因此,當(dāng)一個(gè)路由匹配成功后,他可能匹配多個(gè)路由記錄
例如,根據(jù)上面的路由配置,/foo/bar 這個(gè) URL 將會(huì)匹配父路由記錄以及子路由記錄。
一個(gè)路由匹配到的所有路由記錄會(huì)暴露為 $route 對象(還有在導(dǎo)航守衛(wèi)中的路由對象)的 $route.matched 數(shù)組。因此,我們需要遍歷 $route.matched
來檢查路由記錄中的 meta 字段。
下面例子展示在全局導(dǎo)航守衛(wèi)中檢查元字段:
router.beforeEach((to, from, next) => { 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({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } } else { next() // 確保一定要調(diào)用 next() } })
數(shù)據(jù)獲取
我的理解就是在哪里獲取數(shù)據(jù),可以再組件里面,也可以在組件的守衛(wèi)里面,也就是組件的生命周期里面。
有時(shí)候,進(jìn)入某個(gè)路由后,需要從服務(wù)器獲取數(shù)據(jù)。例如,在渲染用戶信息時(shí),你需要從服務(wù)器獲取用戶的數(shù)據(jù)。我們可以通過兩種方式來實(shí)現(xiàn):
導(dǎo)航完成之后獲?。合韧瓿蓪?dǎo)航,然后在接下來的組件生命周期鉤子中獲取數(shù)據(jù)。在數(shù)據(jù)獲取期間顯示『加載中』之類的指示。
導(dǎo)航完成之前獲?。簩?dǎo)航完成前,在路由進(jìn)入的守衛(wèi)中獲取數(shù)據(jù),在數(shù)據(jù)獲取成功后執(zhí)行導(dǎo)航。
從技術(shù)角度講,兩種方式都不錯(cuò) —— 就看你想要的用戶體驗(yàn)是哪種。
導(dǎo)航完成后獲取數(shù)據(jù)
當(dāng)你使用這種方式時(shí),我們會(huì)馬上導(dǎo)航和渲染組件,然后在組件的 created 鉤子中獲取數(shù)據(jù)。這讓我們有機(jī)會(huì)在數(shù)據(jù)獲取期間展示一個(gè) loading 狀態(tài),還可以在不同視圖間展示不同的 loading 狀態(tài)。
假設(shè)我們有一個(gè) Post 組件,需要基于 $route.params.id 獲取文章數(shù)據(jù):
<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> </div> </template> export default { data () { return { loading: false, post: null, error: null } }, created () { // 組件創(chuàng)建完后獲取數(shù)據(jù), // 此時(shí) data 已經(jīng)被 observed 了 this.fetchData() }, watch: { // 如果路由有變化,會(huì)再次執(zhí)行該方法 '$route': 'fetchData' }, 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 { this.post = post } }) } } }
在導(dǎo)航完成前獲取數(shù)據(jù)
通過這種方式,我們在導(dǎo)航轉(zhuǎn)入新的路由前獲取數(shù)據(jù)。我們可以在接下來的組件的 beforeRouteEnter 守衛(wèi)中獲取數(shù)據(jù),當(dāng)數(shù)據(jù)獲取成功后只調(diào)用 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)) }) }, // 路由改變前,組件就已經(jīng)渲染完了 // 邏輯稍稍不同 beforeRouteUpdate (to, from, next) { this.post = null getPost(to.params.id, (err, post) => { this.setData(err, post) next() }) }, methods: { setData (err, post) { if (err) { this.error = err.toString() } else { this.post = post } } } }
在為后面的視圖獲取數(shù)據(jù)時(shí),用戶會(huì)停留在當(dāng)前的界面,因此建議在數(shù)據(jù)獲取期間,顯示一些進(jìn)度條或者別的指示。如果數(shù)據(jù)獲取失敗,同樣有必要展示一些全局的錯(cuò)誤提醒。
總結(jié)
以上所述是小編給大家介紹的vue-router相關(guān)基礎(chǔ)知識及工作原理,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問題的方法
本文主要介紹了Vue-Cli配置代理轉(zhuǎn)發(fā)解決跨域問題的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06Vue使用Element-UI實(shí)現(xiàn)分頁效果全過程
element-ui官網(wǎng)上有分頁實(shí)現(xiàn)的功能,簡單方便又好用,也有很多分頁的樣式,你可以根據(jù)需要去選擇自己想要的樣式,下面這篇文章主要給大家介紹了關(guān)于Vue使用Element-UI實(shí)現(xiàn)分頁效果的相關(guān)資料,需要的朋友可以參考下2023-04-04Vue為什么要謹(jǐn)慎使用$attrs與$listeners
這篇文章主要介紹了Vue為什么要謹(jǐn)慎使用$attrs與$listeners,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Vue3響應(yīng)式方案及ref?reactive的區(qū)別詳解
眾所周知ref和reactive都是用來作響應(yīng)式數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于Vue3響應(yīng)式方案及ref?reactive區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12vue-router路由參數(shù)刷新消失的問題解決方法
本篇文章主要介紹了vue-router路由參數(shù)刷新消失的問題解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06vue3的自定義指令directives實(shí)現(xiàn)
本文主要介紹了vue3的自定義指令directives實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07vue 動(dòng)態(tài)組件(component :is) 和 dom元素限制(is)用法說明
這篇文章主要介紹了vue 動(dòng)態(tài)組件(component :is) 和 dom元素限制(is)用法說明,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09