vue-router进阶:路由使用归纳、路由导航守卫、导航守卫解析流程、
一、定義和理解
導航守衛的作用:vue-router?提供的導航守衛主要用來通過跳轉或取消的方式守衛導航。
》》項目中應用場景舉例:路由頁面跳轉時候進行登陸驗證;路由跳轉判斷;
?
有多種機會植入路由導航過程中:全局路由, 單個路由獨享的, 或者組件級的。
全局守衛包括:router.beforeEach(是全局前置守衛)、router.beforeResolve(是全局解析守衛)、router.afterEach(是全局后置鉤子)
單個路由獨享的導航守衛:在路由配置上直接定義 beforeEnter?守衛
組件內守衛包括:在組件內定義路由的導航守衛,有beforeRouteEnter 、beforeRouteUpdate 、beforeRouteLeave
?
每個導航守衛都接受3個參數 from 、to、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() 注冊過的回調。
?
注意事項:
1、確保要調用?next?方法,否則鉤子就不會被 resolved。
2、當一個導航觸發時,導航守衛按照順序調用。守衛是異步解析執行,導航在所有守衛 resolve 完之前一直處于 等待中。
?
?
二、導航守衛使用
?
首先vue-router知識回顧:
0、在vue項目中使用vue-router步驟概述:
// 0. 如果使用模塊化機制編程,導入Vue和VueRouter,要調用 Vue.use(VueRouter)// 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 } ]// 3. 創建 router 實例,然后傳 `routes` 配置 // 你還可以傳別的配置參數, 不過先這么簡單著吧。 const router = new VueRouter({routes // (縮寫) 相當于 routes: routes })// 4. 創建和掛載根實例。 // 記得要通過 router 配置參數注入路由, // 從而讓整個應用都有路由功能 const app = new Vue({router }).$mount('#app')// 現在,應用已經啟動了!?
1、可以在任何組件內通過?this.$router?訪問路由器,也可以通過?this.$route?訪問當前路由:
// Home.vue export default {computed: {username () {// 我們很快就會看到 `params` 是什么return this.$route.params.username}},methods: {goBack () {window.history.length > 1? this.$router.go(-1): this.$router.push('/')}} }?
2、動態路由匹配時使用this.$route.params獲取路由的路徑參數
應用場景:需要把某種模式匹配到的所有路由,全都映射到同個組件。例如,我們有一個?User?組件,對于所有 ID 各不相同的用戶,都要使用這個組件來渲染。又像?/user/foo?和?/user/bar?都將映射到相同的路由。
?
注意:可以在一個路由中設置多段“路徑參數”,如下:
| /user/:username | /user/evan | { username: 'evan' } |
| /user/:username/post/:post_id | /user/evan/post/123 | { username: 'evan', post_id: '123' } |
當使用路由參數時,例如從?/user/foo?導航到?/user/bar,原來的組件實例會被復用。因為兩個路由都渲染同個組件,比起銷毀再創建,復用則顯得更加高效。不過,這也意味著組件的生命周期鉤子不會再被調用。
參數或查詢的改變并不會觸發進入/離開的導航守衛。可以通過觀察?$route?對象來應對這些變化,或使用??beforeRouteUpdate?的組件內守衛。
?
3、復用組件時,想對路由參數的變化作出響應的話,你可以簡單地 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()} }?
4、在router配置文件中使用通配符 (*)捕獲所有路由或 404 Not found 路由
注意:通常含有通配符的路由應該放在最后。路由?{ path: '*' }?通常用于客戶端 404 錯誤。如果你使用了History 模式,請確保正確配置你的服務器。
同一個路徑可以匹配多個路由,此時,匹配的優先級就按照路由的定義順序:誰先定義的,誰的優先級就最高。
當使用一個通配符時,$route.params?內會自動添加一個名為?pathMatch?參數。它包含了 URL 通過通配符被匹配的部分:
// 給出一個路由 { path: '/user-*' } this.$router.push('/user-admin') this.$route.params.pathMatch // 'admin' // 給出一個路由 { path: '*' } this.$router.push('/non-existing') this.$route.params.pathMatch // '/non-existing'?
5、使用vue-router嵌套路由時,在路由定義文件中使用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}]}] })?
6、vue-router路由導航可以在組件中使用<router-link :to="...">?的形式外,還有JS編程式的導航(包括可以調用?this.$router.push(location, onComplete?, onAbort?) 和?router.replace(location, onComplete?, onAbort?),以及router.go(n)?)
注意事項:
- 如果提供了?path,params?會被忽略;
- 如果目的地和當前路由相同,只有參數發生了改變 (比如從一個用戶資料到另一個?/users/1?->?/users/2),你需要使用?beforeRouteUpdate?來響應這個變化 (比如抓取用戶信息);
- router.push?方法。這個方法會向 history 棧添加一個新的記錄,所以,當用戶點擊瀏覽器后退按鈕時,則回到之前的 URL。router.replace?方法它不會向 history 添加新記錄,而是跟它的方法名一樣 —— 替換掉當前的 history 記錄。
- 在 2.2.0+,可選的在?router.push?或?router.replace?中提供?onComplete?和?onAbort?回調作為第二個和第三個參數。這些回調將會在導航成功完成 (在所有的異步鉤子被解析之后) 或終止 (導航到相同的路由、或在當前導航完成之前導航到另一個不同的路由) 的時候進行相應的調用。
router.push、?router.replace?和?router.go?跟?window.history.pushState、?window.history.replaceState?和?window.history.go好像, 實際上它們確實是效仿?window.historyAPI 的。
?
7、當需要在一個組件頁面展示多個路由視圖 router-view ,需要給視圖命名:
<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> const router = new VueRouter({routes: [{path: '/',components: {default: Foo,a: Bar,b: Baz}}] })注意:正確使用?components配置 (帶上 s):
?
8、vue-router路由重定向和別名使用
“重定向”的意思是,當用戶訪問?/a時,URL 將會被替換成?/b,然后匹配路由為?/b;重定向不能指向自身,會形成死循環。
“別名”的意思是給路由取“小名”,/a?的別名是?/b,意味著,當用戶訪問?/b?時,URL 會保持為?/b,但是路由匹配則為?/a,就像用戶訪問?/a?一樣。
const router = new VueRouter({routes: [{ path: '/a', redirect: '/b' },{ path: '/b', redirect: { name: 'foo' }},{ path: '/c', redirect: to => {// 方法接收 目標路由 作為參數// return 重定向的 字符串路徑/路徑對象}},{ path: '/a', component: A, alias: '/b' }] })?
9、vue-router使用路由進行組件傳參時,通過props取代 $route.params 可以降低耦合度:
注意:在組件中使用?$route?會使之與其對應路由形成高度耦合,從而使組件只能在某些特定的 URL 上使用,限制了其靈活性。通過props取代,這樣你便可以在任何地方使用該組件,使得該組件更易于重用和測試。
如果?props?被設置為?true,route.params?將會被設置為組件屬性。
const User = { //$route 的耦合template: '<div>User {{ $route.params.id }}</div>' } const router = new VueRouter({routes: [{ path: '/user/:id', component: User }] })const User = { //通過 props 解耦props: ['id'],template: '<div>User {{ id }}</div>' } const router = new VueRouter({routes: [{ path: '/user/:id', component: User, props: true },// 對于包含命名視圖的路由,你必須分別為每個命名視圖添加 `props` 選項:{path: '/user/:id',components: { default: User, sidebar: Sidebar },props: { default: true, sidebar: false }}] })如果?props?是一個對象,它會被按原樣設置為組件屬性。當?props?是靜態的時候有用。
const router = new VueRouter({routes: [{ path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } }] })?
你可以創建一個函數返回?props。這樣你便可以將參數轉換成另一種類型,將靜態值與基于路由的值結合等等。
const router = new VueRouter({routes: [{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }] })?
?
開始導航守衛使用demo:
注意事項:注意導航守衛并沒有應用在跳轉路由上,而僅僅應用在其目標上。
1、全局前置守衛 router.beforeEach
const router = new VueRouter({ ... })router.beforeEach((to, from, next) => {// ... })?
2、全局解析守衛?router.beforeResolve
在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之后,解析守衛就被調用。
?
?
3、全局后置鉤子router.afterEach
和守衛不同的是,這些鉤子不會接受?next?函數也不會改變導航本身:
router.afterEach((to, from) => {// ... })?
4、路由獨享的守衛router.beforeEnter
是在路由配置上直接定義?beforeEnter?守衛:
const router = new VueRouter({routes: [{path: '/foo',component: Foo,beforeEnter: (to, from, next) => {// ...}}] })?
5、組件內的守衛?beforeRouteEnter、beforeRouteUpdate?(2.2 新增)、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?是支持給?next?傳遞回調的唯一守衛。對于?beforeRouteUpdate?和?beforeRouteLeave?來說,this?已經可用了,所以不支持傳遞回調,因為沒有必要了。
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)} }?
?
三、導航守衛解析流程:
?
?
?
四、其他:路由元信息、過渡動效、數據獲取、滾動位置、懶加載
?
路由元信息:
定義路由的時候可以設置meta字段,可以存儲該路由相關信息(例如:設置每個路由的title,取路由的title設置為選項卡的標題);還可以用來設置頁面權限要求
{path: '/router2',name: 'router2',component:router2,meta:{title:"router2"}} // 全局前置守衛 router.beforeEach((to,from,next) => {console.log(to);console.log(from);if(to.meta.title) {document.title = to.meta.title;} else {document.title = '我是默認的title'}next(); });?
?
路由過渡動效:
Vue 在插入、更新或者移除 DOM 時,提供多種不同方式的應用過渡效果。
const Foo = {template: `<transition name="slide"><div class="foo">...</div></transition>` }const Bar = {template: `<transition name="fade"><div class="bar">...</div></transition>` }<!-- 使用動態的 transition name --> <transition :name="transitionName"><router-view></router-view> </transition>// 接著在父組件內 // watch $route 決定使用哪種過渡 watch: {'$route' (to, from) {const toDepth = to.path.split('/').lengthconst fromDepth = from.path.split('/').lengththis.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'} }?
?
路由數據獲取:(分別有導航完成前和完成后獲取)
有時候,進入某個路由后,需要從服務器獲取數據。例如,在渲染用戶信息時,你需要從服務器獲取用戶的數據。我們可以通過兩種方式來實現:
- 導航完成之后獲取:先完成導航,然后在接下來的組件生命周期鉤子中獲取數據。在數據獲取期間顯示“加載中”之類的指示。
- 導航完成之前獲取:導航完成前,在路由進入的守衛中獲取數據,在數據獲取成功后執行導航。從技術角度講,兩種方式都不錯 —— 就看你想要的用戶體驗是哪種。
導航完成后獲取數據,我們會馬上導航和渲染組件,然后在組件的?created?鉤子中獲取數據。
export default {data () {return {loading: false,post: null,error: null}},created () {// 組件創建完后獲取數據,// 此時 data 已經被 observed 了this.fetchData()},watch: {// 如果路由有變化,會再次執行該方法'$route': 'fetchData'},methods: {fetchData () {this.error = this.post = nullthis.loading = true// replace getPost with your data fetching util / API wrappergetPost(this.$route.params.id, (err, post) => {this.loading = falseif (err) {this.error = err.toString()} else {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))})},// 路由改變前,組件就已經渲染完了// 邏輯稍稍不同beforeRouteUpdate (to, from, next) {this.post = nullgetPost(to.params.id, (err, post) => {this.setData(err, post)next()})},methods: {setData (err, post) {if (err) {this.error = err.toString()} else {this.post = post}}} }?
路由滾動行為:
使用前端路由,當切換到新路由時,想要頁面滾到頂部,或者是保持原先的滾動位置,就像重新加載頁面那樣。?vue-router?能做到,而且更好,它讓你可以自定義路由切換時頁面如何滾動。
注意: 這個功能只在支持?history.pushState?的瀏覽器中可用。
當創建一個 Router 實例,你可以提供一個?scrollBehavior?方法:
const router = new VueRouter({routes: [...],scrollBehavior (to, from, savedPosition) {// return 期望滾動到哪個的位置} })scrollBehavior?方法接收?to?和?from?路由對象。第三個參數?savedPosition?當且僅當?popstate導航 (通過瀏覽器的 前進/后退 按鈕觸發) 時才可用。
這個方法返回滾動位置的對象信息,長這樣:
- { x: number, y: number }
- { selector: string, offset? : { x: number, y: number }}?(offset 只在 2.6.0+ 支持)
如果返回一個 falsy (譯者注:falsy 不是?false,參考這里)的值,或者是一個空對象,那么不會發生滾動。
舉例:
scrollBehavior (to, from, savedPosition) {return { x: 0, y: 0 } }對于所有路由導航,簡單地讓頁面滾動到頂部。
返回?savedPosition,在按下 后退/前進 按鈕時,就會像瀏覽器的原生表現那樣:
scrollBehavior (to, from, savedPosition) {if (savedPosition) {return savedPosition} else {return { x: 0, y: 0 }} }如果你要模擬“滾動到錨點”的行為:
scrollBehavior (to, from, savedPosition) {if (to.hash) {return {selector: to.hash}} }?
?
路由懶加載:
當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件,這樣就更加高效了。
結合 Vue 的異步組件和 Webpack 的代碼分割功能,輕松實現路由組件的懶加載。
一個能夠被 Webpack 自動代碼分割的異步組件。
const Foo = () => import('./Foo.vue')在路由配置中什么都不需要改變,只需要像往常一樣使用?Foo:
const router = new VueRouter({routes: [{ path: '/foo', component: Foo }] })?
?
?
五、應用demo
vue路由登陸驗證:
前言:vue的項目的登錄狀態如果用vuex狀態管理,頁面一刷新vuex管理的狀態就會消失,所以,會將登錄的狀態寫到web Storage中進行存儲管理。
步驟:
1、router/index.js 》在需要登錄驗證的路由元信息里加入登錄驗證標識requiresAuth:true
routers: [{ path: '/home',name: 'home',component: Home,meta: { requiresAuth: true }},{path: '/login',name: 'Login',component: Login},]2、登陸組件從服務端獲取到token,將登錄狀態寫入web Storage里
login() {this.$api.login().then(function(res) {//調用axios二次封裝的login方法,關閉mockjs時會傳遞到nodejssessionStorage.setItem('accessToken', res.data.token) // 成果返回后放置token到Cookie //console.log("登陸響應",JSON.stringify(res.data));router.push('/home') // 登錄成功,跳轉到主頁}).catch(function(res) {alert(res);});},?
3、router/index.js 》再路由配置文件中使用全局前置導航守衛
//全局前置導航守衛,導航跳轉前進行token登陸令牌判斷 router.beforeEach((to, from, next) => {if(to.path === '/login') { next() }else {if(to.meta.requiresAuth && !sessionStorage.getItem('accessToken')) {next({ path: '/login' })}else { //如果不需要登錄驗證,或者已經登錄成功,則直接放行next() } }});?
?
官網API:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
?
總結
以上是生活随笔為你收集整理的vue-router进阶:路由使用归纳、路由导航守卫、导航守卫解析流程、的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C指针原理(35)-Ncurses-文本
- 下一篇: MySQL如何有效的存储IP地址