? 通常我們會用一個或多個路由表,來匹配所有頁面的路徑。但這不能滿足一些特定的需求。
# 解惑
? 好幾次被問到怎么區(qū)分$route
和 $router
,這里統(tǒng)一給出解釋:
-
this.$router:指的是
router
實例蜂筹,也就是new VueRouter()
的執(zhí)行結果需纳,使用效果與router
相同。有這種寫法是因為Vue并不想在每個獨立需要封裝路由的組件中都導入路由艺挪,因此在全局創(chuàng)建了該對象不翩。 -
this.$route:指的是當前 激活 的 路由信息對象,又稱路由記錄麻裳,該對象是只讀的口蝠,內部屬性不可改變,當路由發(fā)生重定向或路由參數(shù)改變時津坑,該對象會被重新計算妙蔗。一般我們會通過
watch
來監(jiān)聽它們。
?
# 基本路由
? 對于基本路由疆瑰,我們需要精準的匹配路由內容眉反,才能實現(xiàn)頁面跳轉昙啄。其編寫風格大致如下
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import 404 from '@/404'
Vue.use(Router)
const router = new Router({
routes: [
{ path: '/', redirect: '/index' },
{ path: '/index', component: HelloWorld, alias: '/home' },
{ path: '/404', meta: { title: '錯誤頁面' }, component: 404 },
{ path: '/b', redirect: to => {
// 接收目標路由to作為參數(shù),return值作為動態(tài)響應的路徑
} }
]
})
export default router
? 在這個案例中寸五,this.$router
就是router
常量梳凛,而this.$route
是在實例化后,即將登場的那個路由對象梳杏,如假設為{ path: '/index'韧拒,component: HelloWorld }
。
? meta
為路由元信息十性,常提供給 導航守衛(wèi) 和this.$route
使用
? alias
是路由別名叛溢,其作用是當匹配到/home
時,訪問的是/home
路由但真正走的是/index
路由的頁面
import為何能實現(xiàn)路由懶加載
? 曾經(jīng)我也困惑過作為編譯時調用的import
是如何實現(xiàn)懶加載的烁试。其實對比
?import('./index.vue')
(編譯時加載)和
?const Index = () => import('./index.vue')
(懶加載)
? 簡單理解:后者編譯時只是聲明了函數(shù)雇初,只有使用時才運行該內容。
? 深入一些:利用Promise 可以將異步組件定義為返回一個Promise 的工廠函數(shù)减响,又因為webpack2中動態(tài)import
能實現(xiàn)代碼分塊靖诗,從而達到定義一個能被Webpack自動代碼分割的異步組件的效果
?
# 動態(tài)路由匹配
? 例如,我們有一個 user
組件支示,對于所有ID各不相同的用戶刊橘,都要使用這個組件來渲染,因為用戶ID未知颂鸿,不可能再路由表里書寫這個路由促绵,這時,就需要使用到 vue-router
中的 “動態(tài)路徑參數(shù)” 來達到這個效果
const router = new VueRouter({
routes: [
// 動態(tài)路徑參數(shù)嘴纺,以冒號開頭
{ path: '/user/:id', component: () => import('@/components/User') }
]
})
? 現(xiàn)在呢败晴,對于如/user/12345
和 /user/e9e0jh830d
類型的路由,都會被映射到相同的路由路徑上栽渴,而 /user/
后的則是本路徑的參數(shù)尖坤,頁面初始化時并不關心它是什么內容,認為這是提供給頁面內部使用的一個參數(shù)
? 既然是參數(shù)闲擦,頁面就應該有能力獲取慢味,通常我們需要根據(jù)這個參數(shù)發(fā)起請求獲取頁面更詳細的渲染數(shù)據(jù),獲取參數(shù)的辦法如下
-
單個參數(shù)
Path:{ path: '/user/:id' }
Url:/user/e9e0jh830d
const para = this.$route.params
// { id: e9e0jh830d}
-
支持多個參數(shù)
Path:{ path: 'user/:username/post/:post_id' }
Url:/user/zhangfs/post/330473
const para = this.$route.params
// { username: zhangfs, post_id: '330473' }
?
# 響應路由參數(shù)的變化!!!
? 有個很經(jīng)典的問題:假設在一個 User 列表中墅冷,用戶切換查看不同賬戶信息纯路,Url上/user/:id
后面的 id
發(fā)生了變化實現(xiàn)了用戶切換,然而頁面本該展示不同用戶信息的數(shù)據(jù)并沒有更新寞忿。
? 原因是Vue在設計初衷驰唬,為了最大化高效利用組件,并不會銷毀后再創(chuàng)建,也就是說定嗓,組件的生命鉤子如Created
將不會再次被觸發(fā)調用蜕琴,這就要求我們自己實現(xiàn)觸發(fā)。筆者提供4種思路
(1)監(jiān)聽路由參數(shù)宵溅,發(fā)生變化則重新獲取頁面渲染數(shù)據(jù)
(2)監(jiān)聽路由(Vue推薦)
watch: {
'$route' (to, from) { ... } // to-from 都能監(jiān)聽出變化做出響應
}
(3)使用組件內導航守衛(wèi) beforeRouteUpdate
beforeRouteUpdate (to, from, next) { ... } // 別忘記調用next()
(4)為<router-view>
增加唯一的key
凌简。這種方式是逆Vue思路來的,設計Key
關鍵字實現(xiàn)銷毀組件重新掛載來保證生命鉤子重新觸發(fā)
<router-view :key="key" />
computed: {
key() {
return this.$route.name ? this.$route.name + new Date() : this.$route + new Date()
}
}
?
# 嵌套的子路由
? 通常恃逻,在app中聲明的路由為 頂級路由雏搂,作為頂層的出口,渲染最高級路由匹配到的組件寇损。
<div id="app">
<router-view></router-view>
</div>
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
? 如果需要實現(xiàn)子路由凸郑,如匹配 /user/:id/posts
,則需要設置嵌套的路由矛市,使用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
}
]
}
]
})
【注意】以/
開頭的路徑會被當做根路徑。也就是說允許你充分的使用嵌套組件而無需設置嵌套的路徑
? 該例中浊吏,當遇到訪問/user
的路徑不會渲染任何東西而昨,是因為沒有匹配到任何合適的子路由。當然你可以提供一個空的子路由來承接該路徑
children: [
{ path: '', component: UserHome }
]
?
# 編程式導航
? 我們知道<router-link>
實際上是創(chuàng)建了a
標簽來實現(xiàn)導航鏈接找田,我們還可以借助 router
的實例方法通過編寫代碼來實現(xiàn)歌憨。注意,如在開篇描述墩衙,組件內部應該使用this.$router
訪問
# this.$router.push()
聲明式 | 編程式 |
---|---|
<router-link :to="..." /> |
this.$router.push(...) |
使用語法: this.$router.push(location, onComplete回調. onAbort回調)
? location
接受一個字符串路徑务嫡,或一個描述地址的對象
this.$router.push('dashboard') // -> /dashboard
this.$router.push({ path: 'dashboard' }) // -> /dashboard
this.$router.push({ name: 'user', parmas: { userId: '123' } }) // -> /user/123
this.$router.push({ path: 'register', query: { plan: 'private' } }) // -> /register?plan=private
【注】注意觀察第三個和第四個,重定向帶路徑參數(shù)的路由時漆改,不能指定 path
心铃,因為提供了 path
后 params
參數(shù)會自動被忽略。如果想要用path
則需要修改如下:
const userId = '123'
this.$router.push({ path: `/user/${userId}` }) // -> /user/123
// 錯誤示例
this.$router.push({ path: `/user`, params: { userId: '123' } }) // -> /user
? 同樣的規(guī)則適用于 <router-link :to="">
的 to
屬性
?
# this.$router.replace()
聲明式 | 編程式 |
---|---|
<router-link :to="..." replace/> |
this.$router.replace(...) |
? 跟 router.push
很像挫剑,唯一的不同就是去扣,它不會向 history
添加新記錄,而是跟它的方法名一樣 —— 替換掉當前的 history
記錄暮顺。
?
# this.$router.go(n)
? 這個方法的參數(shù)是一個整數(shù),意思是在 history
記錄中向前或者后退多少步秀存,類似 window.history.go(n)
捶码。
this.$router.go(1) // 前進一步
this.$router.go(-1) // 后退一步
this.$router.go(-100) // 如果記錄不夠用,默默失敗不阻塞
?
# 常用路由對象屬性
-
當前路由 -
$route.path
Path:{ path: '/user/:id' }
Url:user/12345
說明:總是解析為絕對路徑或链,無視是否為路徑參數(shù)惫恼。返回對象
const path = this.$route.path
// user/12345
-
url參數(shù) -
$route.query
Path:{ path: '/user' }
Url:/user?id=e9e0jh830d
const query = this.$route.query
// { id: e9e0jh830d }
-
路由參數(shù)-
$route.params
Path:{ path: '/user/:id' }
Url:/user/12345
const para = this.$route.params
// { id: 12345 }
?
# 常用路由導航守衛(wèi)
- 全局前置守衛(wèi) -
router.beforeEach
? 這是最常使用也是最重要的守衛(wèi)之一,使用時要調用 next()
方法表示導航 resolved
澳盐;因為守衛(wèi)是異步解析執(zhí)行祈纯,沒有resolve
的導航守衛(wèi)處在等待中阻斷執(zhí)行進程令宿,路由也不會實現(xiàn)跳轉
next()
允許接收一個參數(shù):(1)false
- 中斷當前導航,(2){ path: '/route_name' }
- 實現(xiàn)跳轉
router.beforeEach((to, from, next) => {
// `to` 和 `from`都是路由對象
})
- 全局后置鉤子 -
router.afterEach
? 與前置導航守衛(wèi)不同的是腕窥,afterEach
并不是“守衛(wèi)”粒没,而是“鉤子”,它不會阻塞線程簇爆,也不需要調用next()
來表達resolve
狀態(tài)癞松。可以認為是線程流水線里的一個作業(yè)而已
router.afterEach((to, from) => { ... })
- 私有路由守衛(wèi) -
router.beforeEnter
? 與beforeEach
和afterEach
不同的是入蛆,beforeEnter
是針對單個路由進行配置的導航守衛(wèi)响蓉,作用于所在路由內而不影響其他路由路徑。該守衛(wèi)也需要調用next()
來表示守衛(wèi)結束狀態(tài)
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
?
? 另外還有 組件內的守衛(wèi) 支持在組件內直接定義導航守衛(wèi)哨毁。需注意的是枫甲,其中的beforeRouterEnter
守衛(wèi)不能訪問this
對象,理由是守衛(wèi)在導航確認前即將登場的新組件并未被創(chuàng)建扼褪,需要通過 next(vm)
中的 vm
參數(shù)來調用組件實例
export default {
data () {
...
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
methods: { ... }
}
- 完整的導航解析流程
- 導航被觸發(fā)想幻。
- 在失活的組件里調用離開守衛(wèi)。
- 調用全局的 beforeEach 守衛(wèi)迎捺。
- 在重用的組件里調用 beforeRouteUpdate 守衛(wèi) (2.2+)举畸。
- 在路由配置里調用 beforeEnter。
- 解析異步路由組件凳枝。
- 在被激活的組件里調用 beforeRouteEnter抄沮。
- 調用全局的 beforeResolve 守衛(wèi) (2.5+)。
- 導航被確認岖瑰。
- 調用全局的 afterEach 鉤子叛买。
- 觸發(fā) DOM 更新。
- 用創(chuàng)建好的實例調用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調函數(shù)
?
# 路由過渡動效
? <router-view>
是基本的動態(tài)組件蹋订,可以用 <transition>
組件增加過渡效果
<transition>
<router-view></router-view>
</transition>
? 以上方式會給所有的路由增加相同的簡單的切換過渡效果率挣,如果想個性化給每個路由增加,則需要在組件內使用 <transition>
并設置不同的 name
實現(xiàn)頁面級的過渡效果
<transition name="fade" mode="out-in">
<router-view/> <!-- 也可以直接寫在某個具體的組件內<template>...</template> -->
</transition>
.fade-enter {
opacity:0;
}
.fade-leave{
opacity:1;
}
.fade-enter-active{
transition:opacity .5s;
}
.fade-leave-active{
opacity:0;
transition:opacity .5s;
}
?
# 路由切換滾動
? 當切換到新路由時露戒,想要頁面滾到頂部椒功,或者是保持原先的滾動位置,就像重新加載頁面那樣智什。 vue-router
能做到动漾,而且更好,它讓你可以自定義路由切換時頁面如何滾動荠锭。該功能需要瀏覽器支持history.pushBehavior
API
const router = new VueRouter({
routes: [...],
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
}
}
}
? 甚至可以配合路由元信息meta
實現(xiàn)更細顆粒度控制滾動旱眯,和利用返回一個Promise來得出預期的位置滾動。