Vue Router 官網(wǎng)已經(jīng)寫得很好了,這里自己再總結(jié)鞏固下夕春。(注意:這里所有的例子都是基于Vue/cli 3.4的腳手架進(jìn)行講解的)
Vue的路由分兩種:編程式路由和聲明式路由
vm.$route.push('/路由地址') //編程式路由
<router-link to="/路由地址"></router-link> //聲明式路由
Vue Router可以分以下幾個模塊來講解
- 動態(tài)路由匹配
- 嵌套路由
- 編程式導(dǎo)航
- 命名路由
- 命名視圖
- 重定向和別名
- 路由組件傳參
- H5 History模式
- 導(dǎo)航守衛(wèi)
- 路由元信息
- 過渡動效(本文不做介紹)
- 數(shù)據(jù)獲取
- 滾動行為
- 路由懶加載
動態(tài)路由匹配
【應(yīng)用場景】
假設(shè)我們有個模板是展示用戶信息的甥桂,在上一個模板中避咆,點(diǎn)擊不同的用戶ID進(jìn)入相同的模板痒芝,但是展示的用戶信息是不一樣的赃春。就像這樣:
/user/user1
和/user/user2
,當(dāng)然還有其他方式匪凡,我們先用這種方式膊畴。看代碼:
<!-- 先寫一個視圖用于放置聲明式路由 -->
<template>
<div>
home
<router-link to="/componentA/user1">user1</router-link>
<router-link to="/componentA/user2">user2</router-link>
</div>
</template>
<script>
// @ is an alias to /src
export default {
}
</script>
//在腳手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import componentA from './components/componentA.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/componentA/:id/', //這里就是動態(tài)路由病游,通過id來進(jìn)行動態(tài)匹配
component: componentA
}
]
})
<!-- componentA -->
<template>
<div>
componentA: {{$route.params.id}}
<button @click="goBack">goBack</button>
</div>
</template>
<script>
export default {
name: 'componentA',
methods: {
goBack() {
this.$router.push('/')
},
}
}
</script>
<style lang="">
</style>
【響應(yīng)路由參數(shù)變化注意點(diǎn)】
- 組件會被復(fù)用
- 組件生命周期鉤子不會被調(diào)用
- 可以通過 watch方法或者 beforeRouteUpdate監(jiān)聽動態(tài)路由變化
watch() {
'$route'(to, from) {
//...
}
},
beforeRouteUpdate(to, from, next) {
console.log("to", to);
console.log("from", from);
console.log("next", next);
},
嵌套路由
【應(yīng)用場景】
比如做個tab頁唇跨,就像下圖這樣:
這個時候就要用到嵌套視圖了,看代碼:
現(xiàn)在App.vue里放個路由視圖(也就是<router-view></router-view
這個組件)
<template>
<!-- App.vue -->
<div id="app">
<router-view></router-view>
</div>
</template>
<style lang="less">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
先寫一個組件充當(dāng)父路由的組件
<template>
<!-- tab.vue -->
<div class="tab">
<router-link to="/index/a">首頁</router-link>
<router-link to="/index/b">技術(shù)頁</router-link>
<!-- 這里很重要衬衬,如果父路由里的組件里沒有相應(yīng)的路由視圖买猖,那么子路由導(dǎo)航后是無法展現(xiàn)的 -->
<router-view></router-view>
</div>
</template>
<script>
import Vue from 'vue';
// @ is an alias to /src
export default {
name: 'tab',
};
</script>
<style scoped>
.tab {
background-color: yellow;
}
</style>
然后隨便寫2個組件視圖
<template>
<!-- componentA_a.vue -->
<div class="index">
<div class="index">
首頁
</div>
</div>
</template>
<script>
export default {
name: "componentA_a",
};
</script>
<style scoped>
.index {
background-color: red;
}
</style>
<template>
<!-- componentA_b.vue -->
<div class="tec">
<div class="tec">技術(shù)頁</div>
</div>
</template>
<script>
export default {
name: "componentA_b",
created() {
console.log(123);
}
};
</script>
<style scoped>
.tec {
background-color: lightblue;
}
</style>
最后配置router.js
import Vue from 'vue'
import Router from 'vue-router'
import tab from './views/tab.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
redirect: '/index' //重定向到/index路由
},
{
path: '/index',
component: tab,
children: [
{
path: '',
redirect: 'a' //重定向路由,當(dāng)路徑是空的時候可以重定向路由也可以提供一個組件
},
{
path: 'a',
name: "componentA_a",
component: () => import('./components/componentA_a.vue')
},
{
path: 'b',
name: "componentA_b",
component: () => import('./components/componentA_b.vue')
}
]
}
]
})
這樣就實現(xiàn)了嵌套路由滋尉,效果就是前面那張圖政勃。
PS:
- 以 / 開頭的嵌套路徑會被當(dāng)作根路徑。 這讓你充分的使用嵌套組件而無須設(shè)置嵌套的路徑兼砖。
- 如果父路由里的組件里沒有相應(yīng)的路由視圖奸远,那么子路由導(dǎo)航后是無法展現(xiàn)的
編程式導(dǎo)航
【應(yīng)用場景】
大多數(shù)情況還是編程式導(dǎo)航的吧,畢竟編程式導(dǎo)航用起來更加靈活讽挟。我可以對任務(wù)組件綁定事件懒叛,觸發(fā)路由導(dǎo)航,而且可以隨性所欲的傳參耽梅。
3個方法
-
router.push(location, onComplete?, onAbort?)
想要導(dǎo)航到不同的 URL薛窥,則使用 router.push 方法。這個方法會向 history 棧添加一個新的記錄眼姐,所以诅迷,當(dāng)用戶點(diǎn)擊瀏覽器后退按鈕時,則回到之前的 URL众旗。
PS: <router-link :to="..."> 等同于調(diào)用 router.push(...)罢杉。
該方法的參數(shù)可以是一個字符串路徑,或者一個描述地址的對象贡歧。例如:
// 字符串
router.push('home')
// 對象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 帶查詢參數(shù)滩租,變成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
注意:如果提供了 path,params 會被忽略利朵,上述例子中的 query 并不屬于這種情況律想。取而代之的是下面例子的做法,你需要提供路由的 name 或手寫完整的帶有參數(shù)的 path:
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
同樣的規(guī)則也適用于 router-link 組件的 to 屬性绍弟。
在 2.2.0+技即,可選的在 router.push 或 router.replace 中提供 onComplete 和 onAbort 回調(diào)作為第二個和第三個參數(shù)。這些回調(diào)將會在導(dǎo)航成功完成 (在所有的異步鉤子被解析之后) 或終止 (導(dǎo)航到相同的路由樟遣、或在當(dāng)前導(dǎo)航完成之前導(dǎo)航到另一個不同的路由) 的時候進(jìn)行相應(yīng)的調(diào)用而叼。
注意: 如果目的地和當(dāng)前路由相同郭脂,只有參數(shù)發(fā)生了改變 (比如從一個用戶資料到另一個 /users/1
-> /users/2
),你需要使用 beforeRouteUpdate
來響應(yīng)這個變化 (比如抓取用戶信息)澈歉。
-
router.replace(location, onComplete?, onAbort?)
PS:<router-link :to="..." replace> 等同于router.replace(...)
跟 router.push 很像展鸡,唯一的不同就是,它不會向 history 添加新記錄埃难,而是跟它的方法名一樣 —— 替換掉當(dāng)前的 history 記錄莹弊。 -
router.go(n)
這個方法的參數(shù)是一個整數(shù),意思是在 history 記錄中向前或者后退多少步涡尘,類似 window.history.go(n)
// 在瀏覽器記錄中前進(jìn)一步忍弛,等同于 history.forward()
router.go(1)
// 后退一步記錄,等同于 history.back()
router.go(-1)
// 前進(jìn) 3 步記錄
router.go(3)
// 如果 history 記錄不夠用考抄,那就默默地失敗唄
router.go(-100)
router.go(100)
命名路由
在前面的例子中你可能已經(jīng)注意到了细疚,比如這樣
router.push({ name: 'user', params: { userId: '123' }})
或者
<router-link :to="{ name: 'user', params: { userId: 123 }}"></router-link>
這2種方法是等效的,這樣就省去了path參數(shù)川梅,
命名視圖
【應(yīng)用場景】
比如你一個路由里的組件需要多個子組件布局疯兼,這幾個子組件是公共復(fù)用的,那么通過命名路由視圖的方式將大大降低你組件的耦合度和發(fā)雜性贫途。
看代碼:
<template>
<!-- App.vue -->
<div id="app">
<router-view name="header"></router-view>
<router-view name="body"></router-view>
</div>
</template>
<style lang="less">
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
//在腳手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import componentA from './components/componentA.vue'
import componentB from './components/componentB.vue'
Vue.use(Router)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
components: { //加個s
body: componentB,
header: componentA,
}
}
]
})
把剛才視圖組件補(bǔ)齊
<template>
<!-- componentA.vue -->
<div>
<div class="header">
header
</div>
</div>
</template>
<script>
export default {
name: 'componentA',
}
</script>
<style lang="less" scoped>
.header {
background-color: lightgreen;
}
</style>
<template>
<!-- componentB.vue -->
<div>
<div class="bodyer">
body
</div>
</div>
</template>
<script>
export default {
name: 'componentB',
}
</script>
<style scoped lang="less">
.bodyer {
background-color: lightgrey;
}
</style>
效果:
重定向和別名
【應(yīng)用場景】
重定向的應(yīng)用場景還是比較多的吧吧彪,經(jīng)常用的就有默認(rèn)路由重定向,別名就是給配置的路由名換個馬甲丢早,隱藏下真實的路由名吧姨裸,其他用處還沒想到。
看重定向例子:
重定向也是通過 routes 配置來完成怨酝,下面例子是從 /a 重定向到 /b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
重定向的目標(biāo)也可以是一個命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
甚至是一個方法傀缩,動態(tài)返回重定向目標(biāo):
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目標(biāo)路由 作為參數(shù)
// return 重定向的 字符串路徑/路徑對象
}}
]
})
看別名例子:
/a 的別名是 /b,意味著农猬,當(dāng)用戶訪問 /b 時赡艰,URL 會保持為 /b,但是路由匹配則為 /a盛险,就像用戶訪問 /a 一樣瞄摊。
絕對別名
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
默認(rèn)別名
const router = new VueRouter({
routes: [
{ path: 'default', component: Default, alias: '' },
]
})
多個別名
const router = new VueRouter({
routes: [
{ path: 'baz', component: Baz, alias: ['/baz', 'baz-alias'] },
]
})
路由組件傳參
先看代碼
<template>
<div>
<router-link to="/b/c">c</router-link>// 這里我傳了個參數(shù)“c”
</div>
</template>
<script>
// @ is an alias to /src
export default {
}
</script>
//在腳手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import componentA from './components/componentA.vue'
import componentB from './components/componentB.vue'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/b/:id',
name: 'b',
component: componentB,
props: (route) => ({ //這里也可以通過props: true, 或者 props: {id : true}來傳參
id: route.params.id
})
}
]
});
export default router;
<template>
<!-- componentB.vue -->
<div>
<div class="bodyer">
body: {{id}}
</div>
</div>
</template>
<script>
export default {
props: ['id'], //通過props接受參數(shù)
name: 'componentB',
}
</script>
<style scoped lang="less">
.bodyer {
background-color: lightgrey;
}
</style>
HTML5 History 模式
Vue-router是基于H5的history模式來實現(xiàn)的
導(dǎo)航守衛(wèi)
記住參數(shù)或查詢的改變并不會觸發(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) => {
// ...
})
to: 即將要進(jìn)入的目標(biāo) 路由對象
from: 當(dāng)前導(dǎo)航正要離開的路由
next`:
next(false),表示終止跳轉(zhuǎn)
next('需要跳轉(zhuǎn)的目標(biāo)路由')
next(可以傳入router.push支持的任意內(nèi)容')
全局解析守衛(wèi)
router.beforeResolve和 router.beforeEach 類似楔壤,區(qū)別是在導(dǎo)航被確認(rèn)之前鹤啡,同時在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后,解析守衛(wèi)就被調(diào)用蹲嚣。
全局后置鉤子
你也可以注冊全局后置鉤子递瑰,然而和守衛(wèi)不同的是祟牲,這些鉤子不會接受 next 函數(shù)也不會改變導(dǎo)航本身:
router.afterEach((to, from) => {
// ...
})
路由獨(dú)享的守衛(wèi)(beforeEnter)
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
組件內(nèi)的守衛(wèi)
- beforeRouteEnter
- beforeRouteUpdate (2.2 新增)
- beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
next(vm => {
console.log(vm)//vm就是組件實例,也就是this
})
// 在渲染該組件的對應(yīng)路由被 confirm 前調(diào)用
// 不抖部!能说贝!獲取組件實例 `this`
// 因為當(dāng)守衛(wèi)執(zhí)行前,組件實例還沒被創(chuàng)建
},
beforeRouteUpdate (to, from, next) {
// 在當(dāng)前路由改變慎颗,但是該組件被復(fù)用時調(diào)用
// 舉例來說乡恕,對于一個帶有動態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時候俯萎,
// 由于會渲染同樣的 Foo 組件傲宜,因此組件實例會被復(fù)用。而這個鉤子就會在這個情況下被調(diào)用夫啊。
// 可以訪問組件實例 `this`
},
beforeRouteLeave (to, from, next) {
// 導(dǎo)航離開該組件的對應(yīng)路由時調(diào)用
// 可以訪問組件實例 `this`
}
}
完整的解析流程
完整的導(dǎo)航解析流程
導(dǎo)航被觸發(fā)函卒。
在失活的組件里調(diào)用離開守衛(wèi)。beforeRouteleave
調(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)建好的實例調(diào)用 beforeRouteEnter 守衛(wèi)中傳給 next 的回調(diào)函數(shù)营搅。
路由元信息
這個東西特別適合權(quán)限檢查云挟,比如說登錄。在路由配置里給每個路由做個標(biāo)記转质,需要檢查權(quán)限的標(biāo)記下园欣,那么不同權(quán)限就可以按照權(quán)限標(biāo)準(zhǔn)瀏覽頁面了,豈不是美滋滋休蟹。
看代碼:
<template>
<!-- Home.vue -->
<div>
歡迎進(jìn)入首頁
</div>
</template>
<script>
// @ is an alias to /src
export default {
}
</script>
//在腳手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import login from './views/login.vue'
import componentA from './components/componentA.vue'
import componentB from './components/componentB.vue'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: {
isRequireLogin: true
},
beforeEnter(to, from, next) { //具體判斷邏輯不寫了
let judeLogin = () => {
let a = Math.random();
console.log(a)
if(a > 0.5) {
return false
}else {
return true
}
}
console.log(to.matched)
if(to.matched.some(record => record.meta.isRequireLogin)) {
if(judeLogin()) {
next()
} else {
next('/login')
}
}else {
next('/login')
}
}
},
{
path: '/login',
name: 'login',
component: login
}
]
});
export default router;
<template>
<!-- login.vue -->
<div>
登錄頁面
</div>
</template>
<script>
// @ is an alias to /src
export default {
methods: {
}
}
</script>
看效果:這里姑且當(dāng)隨機(jī)數(shù)大于0.5的時候沸枯,顯示登錄失敗
數(shù)據(jù)獲取
有時候,進(jìn)入某個路由后赂弓,需要從服務(wù)器獲取數(shù)據(jù)绑榴。例如,在渲染用戶信息時盈魁,你需要從服務(wù)器獲取用戶的數(shù)據(jù)翔怎。我們可以通過兩種方式來實現(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ù)據(jù)快的話用后面一種宣脉,快的話用前面那種。
第一種不用說吧剔氏,直接在生命鉤子create()里來實現(xiàn)脖旱,后面那種看代碼:
beforeRouteEnter (to, from, next) {
//getPost是自定義的請求接口
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
只要在beforeRouteEnter 這個路由守衛(wèi)里實現(xiàn)就好了
路由懶加載
當(dāng)打包構(gòu)建應(yīng)用時,Javascript 包會變得非常大介蛉,影響頁面加載萌庆。如果我們能把不同路由對應(yīng)的組件分割成不同的代碼塊,然后當(dāng)路由被訪問的時候才加載對應(yīng)組件币旧,這樣就更加高效了践险。
結(jié)合 Vue 的異步組件和 Webpack 的代碼分割功能,輕松實現(xiàn)路由組件的懶加載吹菱。
慶幸的是Vue/cli腳手架默認(rèn)就是懶加載(webpack的方式)巍虫,其他方式我暫時用不到,有興趣的話可以看官網(wǎng)鳍刷。
【例子】
import('./Foo.vue') // 返回 Promise
【完】