vue-router是vue全家桶中,用來控制路由的插件
安裝
依然使用npm進行安裝
npm install vue-router --save
基本使用方式
// 在項目目錄建立兩個文件pageA.vue和pageB.vue
// pageA.vue內(nèi)容如下
<template>
<div>
<p>{{content}}</p>
</div>
</template>
<script>
export default {
data () {
return {
content: '這是頁面A'
}
}
}
</script>
...
// pageB.vue內(nèi)容如下
<template>
<div>
<p>{{content}}</p>
</div>
</template>
<script>
export default {
data () {
return {
content: '這是頁面B'
}
}
}
</script>
...
// index.js內(nèi)容調(diào)整如下
import Vue from 'vue';
import app from './index.vue';
import VueRouter from 'vue-router'; // 引入vue-router
Vue.use(VueRouter); // 引用VueRouter
import pageA from './pageA.vue';
import pageB from './pageB.vue';
const routes = [
{ path: '/pageA', component: pageA },
{ path: '/pageB', component: pageB }
];
const router = new VueRouter({
routes // (簡寫)相當于 routes: routes,所以這個變量名在簡寫下是固定的不可修改,如果用key:value的形式,key值保持這個名詞,value對應的變量名可變(routes: newName)
});
let vm = new Vue({
el: "#app",
router,
render: h => h(app)
});
...
// index.vue內(nèi)容調(diào)整如下
// 因為我們在index.js中設置render渲染區(qū)域是index.vue,所以在index.vue中router-view針對是當前組件
// 如果你在index.js的初始化函數(shù)中設定render: h => h('router-view'),同時在routes中設置一個默認展示{ path: '/', component: app },那么此時index.vue中的router-view是無效
// to="/pageA"和to="pageA"跳轉(zhuǎn)結(jié)果是不一致的,'/pageA'是在路由的根目錄替換路由,'pageA'是在當前路由添加路由
<template>
<div class="wrap">
<div class="title">
<router-link to="/pageA">Go to pageA</router-link>
<router-link to="/pageB">Go to pageB</router-link>
</div>
<div class="content">
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
data () {
return {
}
},
computed: {
},
methods: {
},
mounted () {
console.log(this.$router); // 在組件內(nèi)可以使用this.$router訪問路由信息
},
created () {
},
components: {
}
}
</script>
router-link: 是設定路由的導航區(qū)域
// 基本參數(shù)
to: 路由的指向地址,點擊時,默認會把to中參數(shù)傳給router.push()方法,實現(xiàn)頁面跳轉(zhuǎn)
replace: 修改默認跳轉(zhuǎn)的方法,把默認的router.push()替換為router.replace(),這樣頁面在跳轉(zhuǎn)時,不會有歷史記錄
tag: router-link默認會渲染為a標簽,使用這個參數(shù)可以指定渲染為何標簽,而且一樣會監(jiān)聽點擊事件
router-view: 設定路由內(nèi)容展示的區(qū)域
路由傳參
在實際開發(fā)中,頁面通過URL獲取一定參數(shù)是比較常見的需求,vue-router可以在routes中設定這些信息
// index.js添加一個可以接收相關(guān)參數(shù)的匹配
const routes = [
{ path: '/pageA', component: pageA },
{ path: '/pageA/:id', component: pageA },
{ path: '/pageB', component: pageB }
];
...
// index.vue添加相關(guān)參數(shù)
<div class="title">
<router-link to="/pageA">Go to pageA</router-link>
<router-link to="/pageA/900864">Go to pageA ID</router-link>
<router-link to="/pageB">Go to pageB</router-link>
</div>
...
// pageA.vue添加對這個參數(shù)的處理
<div>
<p>{{content}} {{ $route.params.id }}</p>
</div>
...
// 在pageA.vue的mounted鉤子函數(shù)中,查看相關(guān)傳參數(shù)
// this.$route.params輸出的就是參數(shù)的對象形式,{id: '900864'}
mounted () {
console.log(this.$route.params);
}
...
// params這個信息只在路由匹配時,才會有輸出,默認沒有值
// 比如這個例子中的pageA和接收參數(shù)的pageA,其實調(diào)用是同一個組件,當處在不同的URL下,只有在到了含參數(shù)的pageA鏈接時,mounted才會有輸出
// 此時你應該留意到了一個問題,就是我們在對pageA和包含id的pageA進行切換時,mounted這個鉤子函數(shù)只會執(zhí)行一次
// 那是因為vue處在這種情況下會盡可能的復用當前組件,并不會銷毀重建,所以鉤子函數(shù)只執(zhí)行一次
// 如果想要對這種變化進行監(jiān)聽,可以使用beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave這幾個鉤子函數(shù)來進行監(jiān)聽
// 代碼做如下改造,next方法是必要的,保證路由可以正常調(diào)用
// to是要進入路由的信息,from是離開的路由信息
beforeRouteEnter (to, from, next) {
console.log('beforeRouteEnter');
console.log(to);
console.log(from);
next();
},
beforeRouteUpdate (to, from, next) {
console.log('beforeRouteUpdate');
console.log(to);
console.log(from);
next();
},
beforeRouteLeave (to, from, next) {
console.log('beforeRouteLeave');
console.log(to);
console.log(from);
next();
},
這樣在進到路由相關(guān)頁面這幾個鉤子函數(shù)就會執(zhí)行,執(zhí)行時機如函數(shù)字名的意思
- beforeRouteEnter是進入到路由時執(zhí)行
- beforeRouteLeave是離開路由時執(zhí)行
- beforeRouteUpdate是路由更新時執(zhí)行
beforeRouteEnter, beforeRouteLeave(上一個路由的beforeRouteLeave是下一個路由的beforeRouteEnter)基本一看就知道如何執(zhí)行,beforeRouteUpdate是在針對同一路由數(shù)據(jù)更新時觸發(fā)
// 在index.vue進行如下調(diào)整
<router-link to="/pageA">Go to pageA</router-link>
<router-link to="/pageA/900864">Go to pageA ID:900864</router-link>
<router-link to="/pageA/9527">Go to pageA ID:9527</router-link>
<router-link to="/pageB">Go to pageB</router-link>
這樣在點擊兩個pageA ID進行切換時就會發(fā)現(xiàn),只會觸發(fā)beforeRouteUpdate的鉤子函數(shù),官方還提供了watch方法來監(jiān)聽同一路由的變化,估計是之前沒有beforeRouteUpdate才有的方法
路由嵌套
Vue本身可以進行多層組件嵌套,路由為了適配這種多層嵌套,路由本身也可以進行嵌套。
// 在項目目錄下新建childPageB-A.vue和childPageB-B.vue
// childPageB-A.vue內(nèi)容如下
<template>
<div>
<p>{{content}}</p>
</div>
</template>
<script>
export default {
data () {
return {
content: '這是頁面B的子頁面A'
}
}
}
</script>
...
// childPageB-B.vue內(nèi)容如下
<template>
<div>
<p>{{content}}</p>
</div>
</template>
<script>
export default {
data () {
return {
content: '這是頁面B的子頁面B'
}
}
}
</script>
...
// index.js路由修改
// 路由中children是用來設置嵌套的子路由,在子路由中path如果添加"/"會被當作根路由
import pageA from './pageA.vue';
import pageB from './pageB.vue';
import pageBA from './childPageB-A.vue';
import pageBB from './childPageB-B.vue';
const routes = [
{ path: '/pageA', component: pageA },
{ path: '/pageA/:id', component: pageA },
{ path: '/pageB', component: pageB, children:[
{ path: 'pageBA', component: pageBA},
{ path: 'pageBB', component: pageBB}
] }
];
...
// index.vue做如下修改,添加到子路由的鏈接
<router-link to="/pageB">Go to pageB</router-link>
<router-link to="/pageB/pageBA">Go to pageBA</router-link>
<router-link to="/pageB/pageBB">Go to pageBB</router-link>
此時在頁面端點擊最后兩個鏈接就會發(fā)現(xiàn)通過嵌套路由,pageB.vue的組件中也具備路由切換能力
路由命名
路由可以進行多層嵌套而且也可以接受參數(shù),這就意味著在某些情況下路由會變的非常冗長,不如下面這種情況。
// 項目目錄下新建childPageA-A.vue,內(nèi)容如下
<template>
<div>
<p>{{content}} {{ $route.params.info }}</p>
</div>
</template>
<script>
export default {
data () {
return {
content: '這是頁面A的子頁面A'
}
}
}
</script>
...
// pageA.vue添加路由渲染區(qū)域
<p>{{content}} {{ $route.params.id }}</p>
<router-view></router-view>
...
// index.js添加如下內(nèi)容
// 引入新建組件
import pageAA from './childPageA-A.vue';
...
// pageA路由添加children
{ path: '/pageA/:id', component: pageA, children: [
{path: ':info', component: pageAA}
] },
...
// index.vue添加如下內(nèi)容
<router-link to="/pageA/9527/info">Go to pageA ID:9527 info:info</router-link>
上例中 to="/pageA/9527/info" 其實并不是一種很好的代碼體驗,這種代碼邏輯給別人看的時候,或者一段時間以后自己會看代碼時,根本就弄不清路由中相關(guān)參數(shù)的意義,針對這種情況,我們可以對路由進行命名,并且通過對象的形式傳遞相關(guān)參數(shù)
// 在index.js下代碼做如下調(diào)整
// 在pageA的子路由添加name屬性
const routes = [
{ path: '/pageA', component: pageA },
{ path: '/pageA/:id', component: pageA, children: [
{path: ':info', name: 'pageA', component: pageAA}
] },
{ path: '/pageB', component: pageB, children:[
{ path: 'pageBA', component: pageBA},
{ path: 'pageBB', component: pageBB}
] }
];
...
// index.vue下路由做調(diào)整
// 注意這里的name屬性設定在子路由中,是因為我們期望的URL是"/pageA/9527/info",params會按'value/value'的形式進行拼接,如果我們這時把name屬性定義在page: 'pageA/:id'這一層,會因為沒有形參接收info值,忽略info的傳參,我們這時把name設定在子路由上,此時完整的路由如:'/pageA/:id/:info'
<router-link :to="{ name: 'pageA', params: { id: '9527', info: 'info'} }">Go to pageA ID:9527 info:info</router-link>
使用這種命名路由可以很清晰的看出我們傳遞的參數(shù)是什么
命名視圖
通過上面的例子,我們可以看出,借助router-view我們可以實現(xiàn),在不同區(qū)域展示不同的內(nèi)容,如果我們在對視圖進行命名,可以很容易實現(xiàn)類似iframe的頁面組合的效果。
// 項目目錄下body.vue和footer.vue
// header.vue內(nèi)容修改如下
<template>
<div>
<p>{{ title }}</p>
</div>
</template>
<script>
export default {
data () {
return {
title: '這是頭部'
}
}
}
</script>
...
// body.vue內(nèi)容如下
<template>
<div>
<p>{{ content }}</p>
</div>
</template>
<script>
export default {
data () {
return {
content: '這是body'
}
}
}
</script>
...
// footer.vue內(nèi)容如下
<template>
<div>
<p>{{ footer }}</p>
</div>
</template>
<script>
export default {
data () {
return {
footer: '這是footer'
}
}
}
</script>
...
// index.js引入新建組件
import header from './header.vue';
import body from './body.vue';
import footer from './footer.vue';
...
// index.js路由做相關(guān)調(diào)整
const routes = [
{ path: '/', components: {
default: pageA,
header: header,
body: body,
footer: footer
}},
...
// index.vue頁面做相關(guān)調(diào)整
<div class="content">
<router-view></router-view>
<router-view name="header"></router-view>
<router-view name="body"></router-view>
<router-view name="footer"></router-view>
</div>
這樣在指定的視圖中,就會渲染指定內(nèi)容,如果router-view沒有設置name就會默認為default
編程式導航
上面我們都是通過router-link的方式進行頁面跳轉(zhuǎn),除了使用這種方式,我們還可以通過router的相關(guān)方法來進行跳轉(zhuǎn)。
router.push(location, onComplete?, onAbort?)
這是插入一條歷史記錄的跳轉(zhuǎn)方式,類似location.href,參數(shù)location是跳轉(zhuǎn)地址,可以是字符形式,也可以是對象形式
// index.vue添加如下代碼
<button @click="goPage">Go to pageA</button>
...
// methods下添加goPage方法
goPage () {
// 效果和to="/pageA"一致
// 字符形式
this.$router.push('/pageA');
// 對象形式
// 渲染為/pageA/9527/info
// 要留意使用path時,需不需要添加'/',這個會影響最終渲染的路由路徑
// this.$router.push({ name: 'pageA', params: { id: '9527', info: 'info'} });
// 使用對象形式,如果使用了path,就會忽略params屬性
// 渲染結(jié)果為/pageA
// this.$router.push({ path: '/pageA', params: { id: '9527', info: 'info'} });
// 使用path時,可以使用query屬性
// 渲染為/pageA?id=9527&info=info
// this.$router.push({ path: '/pageA', query: { id: '9527', info: 'info'} });
// 對象形式使用name時,params和query都可以使用
// 渲染為/pageA/9527/info?id=9527&info=info
// this.$router.push({ name: 'pageA', params:{id: '9527', info: 'info'}, query: { id: '9527', info: 'info'} });
}
至于onComplete和onAbort是在路由跳轉(zhuǎn)完成和失敗時分別執(zhí)行的回調(diào)函數(shù)
// 添加onComplete方法
// 此時頁面完成跳轉(zhuǎn)后觸發(fā)onComplete
// onAbort應該是在路由被終止時觸發(fā),沒想到怎么寫這例子...
goPage () {
this.$router.push({ path: '/pageA' }, this.onComplete);
},
onComplete () {
console.log('頁面完成跳轉(zhuǎn)')
}
router.replace(location, onComplete?, onAbort?)
使用方式和push一致,區(qū)別在于replace不會產(chǎn)生新的瀏覽器歷史記錄,和location.replace類似
router.go
和history.go類似,接收一個數(shù)字表示進行歷史記錄前進幾個后退幾個
重定向
把某個路由指向到指定路由,使用redirect參數(shù)設定
// 在index.js的路由中添加如下信息
// 下面表示如果用戶訪問'/goPageA'會指向'/pageA'
{ path: '/goPageA', redirect: '/pageA' },
...
// index.vue的模板中添加如下信息
<router-link to="/goPageA">Go to goPageA</router-link>
這時當我們點擊頁面中這個按鈕時,頁面會自動重定向到'/pageA',重定向還可以使用對象形式,并且可以傳參數(shù)
// index.js中路由信息修改
// 下面信息重定向到/pageA/9527/info
{ path: '/goPageA', redirect: {name: 'pageA', params: {id: 9527, info: 'info'}} },
別名
把某些比較長的路由信息設定一個訪問別名
// 在index.js的路由進行如下修改
// 對'/pageB/pageBB'這個路由設定一個'/BB'的別名
{ path: '/pageB', component: pageB, children:[
{ path: 'pageBA', component: pageBA},
{ path: 'pageBB', component: pageBB, alias: '/BB'}
] }
...
// 在index.vue代碼修改如下
<router-link to="/BB">Go to BB</router-link>
這時如果我們點擊該按鈕,頁面會切換到pageBB的組件中,并且URL上路由為'/BB',當我們點擊"Go to pageBB"鏈接,頁面URL會變成'/pageB/pageBB',但頁面內(nèi)容不變,比較有意思的是這時vue-router會觸發(fā)beforeRouteUpdate的鉤子函數(shù)
組件與路由的解耦
回看我們之前設定的pageA組件
// $route.params.id是從路由上取id參數(shù)來使用,這樣很方便,但也限制了這個組件的復用
<p>{{content}} {{ $route.params.id }}</p>
...
// 通過以下修改,我們把路由相關(guān)的參數(shù)提出props
// pageA.vue頁面修改如下
<div>
<p>{{content}} {{ id }}</p>
<router-view></router-view>
</div>
...
props: ['id'],
// childPageA-A.vue頁面修改如下
<p>{{content}} {{ info }}</p>
...
props: ['info']
...
// index.js中路由做如下修改
// props設為true把$route.params轉(zhuǎn)為了組件屬性,
{ path: '/pageA/:id', component: pageA, props: true, children: [
{path: ':info', name: 'pageA', props: true, component: pageAA}
] },
// 如果把props設為一個對象,就會直接像組件傳遞這個對象中的數(shù)據(jù),而忽略傳遞的參數(shù)
// 比如下面這樣寫,則頁面中所有的id都會被設為9876,這個特性在設定一些靜態(tài)參數(shù)時比較好用
{ path: '/pageA/:id', component: pageA, props: { id: 9876 }, children: [
{path: ':info', name: 'pageA', props: true, component: pageAA}
] },
這時我們點擊相關(guān)鏈接,發(fā)現(xiàn)路由傳遞的參數(shù)依然可以正常使用,而且通過上面的改造,上面的兩個組件也與路由進行了解耦,可以當作普通組件使用
導航的跳轉(zhuǎn)監(jiān)聽
vue-router提供了不少鉤子函數(shù)來監(jiān)聽導航的跳轉(zhuǎn)
在實例化對象上的監(jiān)聽:beforeEach、beforeResolve、afterEach
// 更改index.js路由配置信息
// 添加在路由配置中調(diào)用的鉤子函數(shù)beforeEnter
{ path: '/pageA/:id', component: pageA, props: true, beforeEnter: (to, from, next) => {
console.log('beforeEnter');
next();
}, children: [
{path: ':info', name: 'pageA', props: true, component: pageAA}
] },
...
// 在index.js添加相關(guān)鉤子函數(shù)
const router = new VueRouter({
routes // (簡寫)相當于 routes: routes
});
// beforeEach是要進入某個路由時最先執(zhí)行的鉤子函數(shù)
router.beforeEach((to, from, next) => {
console.log('beforeEach');
console.log(to);
console.log(from);
next();
});
// beforeResolve是在組件路由的鉤子函數(shù)執(zhí)行完成后,執(zhí)行這個鉤子函數(shù)
router.beforeResolve((to, from, next) => {
console.log('beforeResolve');
console.log(to);
console.log(from);
next();
});
// afterEach是在實例化路由和組件路由都執(zhí)行完成后,最后執(zhí)行的鉤子函數(shù),這個鉤子函數(shù)不包含next
router.afterEach((to, from) => {
console.log('afterEach');
console.log(to);
console.log(from);
});
...
// 這時路由相關(guān)的鉤子函數(shù)執(zhí)行如下
// 進入頁面默認執(zhí)行
beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach
...
// 隨意切換路由
beforeRouteLeave -> beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach
// 在同一級路由切換,這時路由模塊并沒有切換所以不會觸發(fā)beforeRouteLeave
beforeEach -> beforeRouteUpdate -> beforeResolve -> afterEach
...
// 按官方文檔的解析,整個路由在進行解析時,執(zhí)行過程如下
1. 導航被觸發(fā)
2. 如果路由進行切換時是準備進到一個新的路由,并且這個新的路由準備渲染的組件和之前路由的渲染組件不一致,就會嘗試去執(zhí)行舊組件中的beforeRouteLeave函數(shù),如果是在同一個路由下,只是傳參不同(比如"/pageA/9527"和"/pageA/900864"),這時并不會執(zhí)行beforeRouteLeave而是直接執(zhí)行第3步
3. 嘗試執(zhí)行路由實例化對象beforeEach函數(shù)
4-1. 如果路由只是參數(shù)不同,這時會嘗試執(zhí)行組件內(nèi)beforeRouteUpdate函數(shù)
4-2-1. 如果路由不同,這時會嘗試執(zhí)行路由配置項中的beforeEnter
4-2-2. 解析異步路由組件(???沒看明白指的是什么)
4-2-3. 嘗試調(diào)用被激活組件的beforeRouteEnter函數(shù)
5. 嘗試調(diào)用路由實例化對象的beforeResolve函數(shù)
6. 導航解析完成
7. 嘗試調(diào)用路由實例化對象的afterEach
8. 觸發(fā)DOM更新
9. 執(zhí)行next回調(diào)
next用來表示當前路由狀態(tài)已經(jīng)被解析,可以繼續(xù)執(zhí)行下面的邏輯,next默認不傳參,如果傳入不同參數(shù),會對路由的執(zhí)行產(chǎn)生不同的阻斷或者跳轉(zhuǎn)。
next(false): 會阻斷后續(xù)函數(shù)執(zhí)行,并且停留在from路由
next('/') 或 next('{ path: "/" }'): 阻斷后續(xù)函數(shù)執(zhí)行,并把路由指向要重定向的頁面
...
// 在index.js的路由中做如下修改
// 這時你點擊與pageA相關(guān)的導航,會自動定向到pageB,并且會把路由解析過程重修執(zhí)行
{ path: '/pageA/:id', component: pageA, props: true, beforeEnter: (to, from, next) => {
console.log('beforeEnter');
next('/pageB');
}, children: [
{path: ':info', name: 'pageA', props: true, component: pageAA}
] },
...
// 在pageA.vue下路由信息添加如下
// next中接收的是一個錯誤的路由信息
beforeRouteEnter (to, from, next) {
console.log('beforeRouteEnter');
console.log(to);
console.log(from);
next(this.printInfo);
},
...
// 如果你在路由對象實例化時注冊了這個函數(shù)
let errMsg = function () {
console.log('路由解析發(fā)生錯誤,可以在這里進行異常處理');
};
router.onError(() => {
console.log('onError');
errMsg();
});
meta信息
在定義路由時,可以設置meta信息來設置一些額外的信息。
// 在index.js的定義路由中,設定相關(guān)meta信息
{ path: '/pageA', component: pageA, meta: { requiresAuth: true } },
...
// 在pageA.vue中mounted獲取定義的meta信息
mounted () {
console.log(this.$route.matched[0].meta);
}
過渡
借用transition標簽使router-view在切換內(nèi)容時,能形成類似app切換的效果
統(tǒng)一過渡效果
// 在index.vue模板中做如下修改
<div class="content">
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
...
// 在index.vue的樣式文件中添加如下樣式
<style>
.fade-enter-active, .fade-leave-active {
transition: all 1s;
position: relative;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-enter {
left: 50px;
}
.fade-leave-to {
left: -50px;
}
.fade-enter-to, .fade-leave {
opacity: 1;
left: 0;
}
</style>
這樣在切換不同組件時就會有app切入切出的效果
不同路由組件的過渡效果
// 清除index.vue下的模板過渡效果
<div class="content">
<router-view></router-view>
...
// pageA.vue下,頁面做下面調(diào)整
<template>
<transition name="silde">
<div>
...
// pageA.vue下添加下面樣式
<style>
.silde-enter-active, .silde-leave-active {
transition: all 1s;
position: relative;
}
.silde-enter {
left: 50px;
}
.silde-leave-to {
left: -50px;
}
.silde-enter-to, .silde-leave {
left: 0;
}
</style>
...
// pageB.vue下添加過渡
<template>
<transition name="opacity">
<div>
...
// pageB.vue添加下面樣式
<style>
.opacity-enter-active, .opacity-leave-active {
transition: all 1s;
position: relative;
}
.opacity-enter, .opacity-leave-to {
opacity: 0;
}
.opacity-enter-to, .opacity-leave {
opacity: 1;
}
</style>
這樣在切換pageA與pageB的相關(guān)路由頁面時,會發(fā)現(xiàn)pageA與pageB會有不同的過渡效果
滾動行為
在切換頁面時,可能會碰到滾動到指定位置的需求,vue-router對此進行了相關(guān)封裝,使用scrollBehavior使頁面滾動到指定位置
// 在index.js中修改代碼邏輯
// 在scrollBehavior中return需要跳轉(zhuǎn)的坐標,這樣在路由切換時就會跳轉(zhuǎn)到指定位置
const router = new VueRouter({
routes, // (簡寫)相當于 routes: routes
scrollBehavior (to, from, savedPosition) {
// return 期望滾動到哪個的位置
return { x: 0, y: 200 }
}
});
...
// 可以通過promise設置延時滾動
scrollBehavior (to, from, savedPosition) {
// return 期望滾動到哪個的位置
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ x: 0, y: 200 })
}, 500)
})
}