簡介
Vue Router 是 Vue 官方指定路由魂那,其賦能 Vue 實(shí)現(xiàn) 單頁應(yīng)用(SPA俯抖,Single Page Application) 前端路由功能。
本文主要介紹下 Vue Router 4.x 的相關(guān)使用方法缸兔。
基本使用
下面通過一個(gè)小例子驅(qū)動(dòng)闡述如何在 Vue3 下使用 Vue Router 4.x。
例子:假設(shè)當(dāng)前頁面有兩個(gè)標(biāo)簽:/home
和/me
学辱,要求點(diǎn)擊不同的標(biāo)簽分別顯示不同的組件頁面缎除。
思路:使用 Vue Router 配置相關(guān)路由,點(diǎn)擊標(biāo)簽時(shí)荒辕,跳轉(zhuǎn)到對(duì)應(yīng)路由視圖汗销。具體操作步驟如下:
-
創(chuàng)建項(xiàng)目:首先創(chuàng)建一個(gè)示例項(xiàng)目:
# 此處使用 vite 進(jìn)行構(gòu)建 $ npm init vite@latest vue3_demos --template vue $ cd vue3_demos # 安裝相關(guān)依賴 $ npm install # 啟動(dòng)應(yīng)用 $ npm run dev
注:Vue3 安裝更多方法,可參考:Vue3 安裝
-
依賴引入:導(dǎo)入 Vue Router 依賴庫:
$ npm install vue-router@4
-
創(chuàng)建組件:分別創(chuàng)鍵
Home.vue
和Me.vue
兩個(gè)組件:<!-- file: components/Home.vue --> <template> <h1>Home Component</h1> </template> <style scoped> h1 { background-color: green; } </style> <!-- file: components/Me.vue --> <template> <h1>Me Component</h1> </template> <style scoped> h1 { background-color: yellow; } </style>
-
創(chuàng)建并配置路由對(duì)象:新建
router/index.js
抵窒,在此創(chuàng)建并配置路由對(duì)象:// file: router/index.js // 導(dǎo)入相關(guān)路由組件對(duì)象 import Home from '../components/Home.vue'; import Me from '../components/Me.vue'; // 定義路由映射:路由映射到具體組件 const routes = [ // 根路徑 / 重定向到 /home { path: '/', redirect: '/home', }, // 前端路由 /home 對(duì)應(yīng)組件 Home { path: '/home', component: Home, }, // 前端路由 /me 對(duì)應(yīng)組件 Me { path: '/me', component: Me, }, ]; // 導(dǎo)入相關(guān)函數(shù) import { createRouter, createWebHashHistory } from 'vue-router'; // 創(chuàng)建路由實(shí)例(`router`)并傳遞路由映射配置(`route`) const router = createRouter({ // 配置導(dǎo)航模式弛针,此處采用 hash 模式 history: createWebHashHistory(), routes, }); // 導(dǎo)出 router 實(shí)例 export default router;
-
裝載 Router 實(shí)例:創(chuàng)建全局
Vue
實(shí)例,并裝載已配置的 Vue Router 實(shí)例:// file: main.js import { createApp } from 'vue'; import App from './App.vue'; import router from './router/index.js'; const app = createApp(App); // 裝載 Vue Router 實(shí)例李皇,確保整個(gè) Vue 應(yīng)用全局支持路由 app.use(router); app.mount('#app');
-
主頁面配置路由導(dǎo)航:主頁面通過
<router-link>
可配置路由導(dǎo)航削茁,匹配的組件會(huì)最終被渲染到<router-view>
中:<!-- file: App.vue --> <template> <h1>Main Page</h1> <div class="nav"> <!-- router-link 最終會(huì)被渲染為一個(gè) a 標(biāo)簽 --> <router-link to="/home">Home</router-link> <router-link to="/me">Me</router-link> </div> <!-- 路由出口:匹配組件最終被渲染位置 --> <router-view /> </template> <style scoped> .nav { width: 100px; display: flex; justify-content: space-around; } </style>
以上,就是一個(gè)簡單的路由導(dǎo)航示例掉房,其效果如下所示:
功能介紹
下面會(huì)對(duì) Vue Router 4.x 提供的一些常見功能進(jìn)行簡介茧跋。
路由對(duì)象
Vue Router 中存在兩個(gè)最主要的路由對(duì)象為:
-
Router
:表示 Vue Router 實(shí)例對(duì)象。在 Vue Router 4.x 中卓囚,使用的是
createRouter()
函數(shù)創(chuàng)建Router
實(shí)例:import { createRouter, createWebHashHistory } from 'vue-router'; const routes = [...]; // 創(chuàng)建路由實(shí)例(`router`)并傳遞路由映射配置(`route`) const router = createRouter({ history: createWebHashHistory(), routes, }); export default router;
Router
主要是提供了對(duì)歷史記錄棧的操作功能瘾杭,比如Router#push
方法可以往歷史堆棧中推入一個(gè)新的 URL,Router#replace
方法可用于替換當(dāng)前的 URL哪亿,還有Router#forward
粥烁、Router#back
、Router#go
...注:當(dāng)代碼中調(diào)用
createApp().use(Router)
時(shí)蝇棉,其實(shí)就向 Vue 實(shí)例全局中注入了一個(gè)Router
實(shí)例讨阻,代碼中獲取該Router
實(shí)例的方法有如下幾種:-
Options API:選項(xiàng)式 API 可通過
this.$routers
獲取全局路由實(shí)例 -
Composition API:組合式 API 可通過函數(shù)
useRouter()
獲取全局路由實(shí)例 -
template:模板中可通過
$router
獲取全局路由實(shí)例
注:創(chuàng)建
Router
時(shí),可設(shè)置一個(gè)激活樣式linkActiveClass
篡殷,這樣在主頁選中<router-link>
時(shí)钝吮,對(duì)應(yīng)標(biāo)簽就會(huì)被添加上自定義激活樣式:// file: router/index.js const router = createRouter({ history: createWebHashHistory(), routes, // 設(shè)置標(biāo)簽激活時(shí),添加樣式類為 activeLink linkActiveClass: 'activeLink', }); <!-- file: App.vue --> <template> <h1>Main Page</h1> <!-- 點(diǎn)擊選中標(biāo)簽時(shí)板辽,自動(dòng)添加 activeLink 類名 --> <router-link to="/home">Home</router-link> <router-link to="/me">Me</router-link> <router-view /> </template> <style scoped> /* 設(shè)置選中樣式 */ .activeLink { background-color: red; } </style>
-
Options API:選項(xiàng)式 API 可通過
-
RouteLocationNormalized
:表示當(dāng)前路由記錄實(shí)例奇瘦。routes
中配置的每條路由映射,我們稱之為「路由記錄」戳气,其類型就是RouteLocationNormalized
:const routes = [ { path: '/home', component: () => import('@/components/Home.vue'), }, { path: '/me', component: Me, }, { path: '/user/:id*', component: () => import('@/components/User.vue'), }, ];
當(dāng)在主頁上點(diǎn)擊選中相應(yīng)路由標(biāo)簽時(shí)链患,就會(huì)跳轉(zhuǎn)到相應(yīng)路由映射組件,此時(shí)可以通過
Router#currentRoute
得到當(dāng)前路由記錄瓶您。比如麻捻,點(diǎn)擊跳轉(zhuǎn)到/user
時(shí)纲仍,Router#currentRoute
就可以獲取/user
路由相關(guān)配置信息。其實(shí)還有其他更方便的方法獲取到當(dāng)前路由地址實(shí)例贸毕,主要包含如下:
-
Options API:對(duì)于選項(xiàng)式 API郑叠,可通過
this.$route
獲取當(dāng)前路由地址實(shí)例 -
Composition API:對(duì)于組合式 API,可通過
useRoute()
函數(shù)獲取當(dāng)前路由地址實(shí)例 -
template:模板中可通過
$route
獲取當(dāng)前路由地址實(shí)例
RouteLocationNormalized
提供了豐富的路由配置選項(xiàng)明棍,這里列舉一些比較常用的:hash
:string
類型乡革,表示當(dāng)前路由hash
部分√福總是以#
開頭沸版,如果 URL 中沒有hash
,則為空字符串兴蒸。path
:string
類型视粮,表示當(dāng)前路由路徑,形如/user/1
fullpath
:string
類型橙凳,表示當(dāng)前路由完整路徑蕾殴,包含path
、query
和hash
部分岛啸,-
name
:類型為RouteRecordName | null | undefined
钓觉,表示當(dāng)前路由名稱。注:建議為每個(gè)路由對(duì)象命名坚踩,方便后續(xù)編程導(dǎo)航荡灾。名稱命名需唯一。
const routes = [ { name: 'user', // 路由命名 path: '/user/:id', component: () => import('@/components/User.vue'), }, ];
redirectedFrom
:類型為RouteLocation | undefined
堕虹,表示觸發(fā)重定向的路由地址卧晓。params
:類型為RouteParams
,用于獲取路徑參數(shù)赴捞。比如對(duì)于/user/:id
,$route.params
獲取到的就是id
對(duì)應(yīng)的信息郁稍。query
:類型為LocationQuery
赦政,表示 URL 查詢參數(shù)。形如/user?id=1
耀怜,則query
對(duì)應(yīng)的就是{id: 1}
-
meta
:類型為RouteMeta
恢着,表示對(duì)當(dāng)前路由的元數(shù)據(jù),即額外信息描述财破。const routes = [ { meta: { name: 'Whyn' }, // 路由元數(shù)據(jù) path: '/user/:id', component: () => import('@/components/User.vue'), }, ];
-
Options API:對(duì)于選項(xiàng)式 API郑叠,可通過
歷史記錄模式
前端路由的改變掰派,其核心是不會(huì)向服務(wù)器發(fā)出請(qǐng)求,Vue Router 提供了兩種模式支持該功能:
-
Hash 模式:Hash 模式 URL 構(gòu)成中帶有一個(gè)哈希字符
#
左痢,更改#
字符后面的路徑不會(huì)發(fā)送請(qǐng)求給服務(wù)器靡羡,其底層是基于瀏覽器的windows.onhashchange
事件系洛。Hash 模式的優(yōu)點(diǎn)是編程簡單,缺點(diǎn)是路徑不夠簡潔(多了額外字符
#
)略步,且對(duì) SEO 不夠友好描扯。Vue Router 中通過
createWebHashHistory()
函數(shù)配置 Hash 模式:import { createRouter, createWebHashHistory } from 'vue-router' const router = createRouter({ history: createWebHashHistory(), routes: [ //... ], })
-
History 模式:History 模式利用了 HTML5 History Interface 中新增的
pushState()
和replaceState()
等方法,實(shí)現(xiàn)了瀏覽器的歷史記錄棧趟薄。調(diào)用這些方法修改 URL 歷史記錄時(shí)绽诚,不會(huì)觸發(fā)瀏覽器向后端發(fā)送請(qǐng)求。History 模式的優(yōu)點(diǎn)是路徑簡潔且控制更靈活杭煎,缺點(diǎn)是 History 模式下恩够,用戶在瀏覽器中直接訪問或手動(dòng)刷新時(shí),會(huì)觸發(fā)真正的請(qǐng)求羡铲,服務(wù)器沒有當(dāng)前請(qǐng)求資源時(shí)玫鸟,會(huì)返回一個(gè)
404
錯(cuò)誤。解決的方法也很簡單犀勒,就是后端添加一個(gè)回退路由屎飘,在 URL 匹配不到任何靜態(tài)資源時(shí),默認(rèn)返回前端頁面的index.html
贾费。比如钦购,Nginx 中可以配置如下:location / { try_files $uri $uri/ /index.html; }
Vue Router 中通過
createWebHistory()
函數(shù)配置啟動(dòng) History 模式:import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ //... ], })
最后,Vue Router 更推薦使用 History 模式褂萧。
路由懶加載
前端每個(gè)路由都會(huì)對(duì)應(yīng)一個(gè)組件押桃,前面我們使用的方式都是導(dǎo)入相應(yīng)組件,然后配置映射到對(duì)應(yīng)路由中:
const Home = { template: '<div>Home</div>' }
const routes = [
{ path: '/', component: Home },
]
當(dāng)路由比較多時(shí)导犹,會(huì)導(dǎo)致加載的組件也變多唱凯,這樣在應(yīng)用打包后,生成的 JavaScript 包會(huì)臃腫變大谎痢,影響頁面加載效率磕昼。
因此,Vue Router 提供了 路由懶加載 功能节猿,其將不同路由對(duì)應(yīng)的組件分割成不同的代碼塊票从,然后當(dāng)路由被訪問時(shí),才動(dòng)態(tài)加載對(duì)應(yīng)組件滨嘱,這樣效率就會(huì)更高峰鄙。
Vue Router 支持開箱即用的動(dòng)態(tài)導(dǎo)入,如下所示:
// 直接加載
import Home from '@/components/Home.vue';
// 懶加載
const Me = () => import('@/components/Me.vue')
const routes = [
{ path: '/home', component: Home, }, // 直接加載
{ path: '/me', component: Me }, // 懶加載
];
注:上述代碼使用@
代表src
目錄太雨,使能需要進(jìn)行如下配置:
// file: vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
const path = require('path');
export default defineConfig({
plugins: [vue()],
resolve: {
// 別名配置
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
此時(shí)如果執(zhí)行構(gòu)建:
$ npm run build
dist/index.html 0.48 KiB
dist/assets/index.4ada91f0.js 2.13 KiB / gzip: 1.13 KiB
dist/assets/Me.a2557100.js 0.23 KiB / gzip: 0.20 KiB # Me.vue
dist/assets/index.d8be49df.css 0.12 KiB / gzip: 0.12 KiB
dist/assets/Me.0aae35d5.css 0.04 KiB / gzip: 0.06 KiB # Me.vue
dist/assets/vendor.fd7d0278.js 71.78 KiB / gzip: 28.44 KiB
可以看到吟榴,配置懶加載組件Me.vue
會(huì)被單獨(dú)打包到一個(gè).js
文件中。
實(shí)際上囊扳,Vue Router 中吩翻,懶加載基本原理是:component
或components
配置接收的是一個(gè)返回Promise
組件的函數(shù)兜看,因此,我們也可以進(jìn)行自定義動(dòng)態(tài)導(dǎo)入仿野,其實(shí)就是創(chuàng)建一個(gè)返回Promise
的函數(shù)铣减,該Promise
返回一個(gè)組件,比如:
const UserDetails = () =>
Promise.resolve({
/* 組件定義 */
})
動(dòng)態(tài)路由
一個(gè)很常見的場景脚作,比如葫哗,根據(jù)用戶id
獲取用戶信息,通常對(duì)應(yīng)的 RESTFul API 為/user/{id}
球涛,即匹配/user/1
劣针、/user/2
...
Vue Router 將這種形式的 URL 稱之為 動(dòng)態(tài)路由,其使用:
進(jìn)行使能亿扁,形式如下所示:
const User = {
template: '<div>User</div>',
}
// 這些都會(huì)傳遞給 `createRouter`
const routes = [
// 動(dòng)態(tài)段以冒號(hào)開始
{ path: '/user/:id', component: User },
]
此時(shí)捺典,上述path
可以匹配/user/1
、/user/username
等等从祝。
動(dòng)態(tài)路由也支持正則匹配襟己,可以設(shè)置更加精細(xì)匹配規(guī)則,常見匹配設(shè)置如下:
const routes = [
// /:id -> 僅匹配數(shù)字
{ path: '/:id(\\d+)' },
// /:username -> 匹配其他任何內(nèi)容
{ path: '/:username' },
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
動(dòng)態(tài)路由信息可以通過$route.params
進(jìn)行獲取牍陌,一個(gè)示例代碼如下:
配置路徑/user/:id(\d+)
映射到組件User.vue
擎浴,并展示id
具體值:
<!-- file: components/User.vue -->
<template>
<!-- 模板獲取 route.params 屬性 -->
<h1>User Component: {{ $route.params }}</h1>
</template>
<script>
import { onBeforeRouteUpdate } from 'vue-router';
export default {
name: 'User',
setup(props, context) {
// Router 鉤子函數(shù)
onBeforeRouteUpdate((to, from, next) => {
// 代碼獲取 route.params 屬性
console.log(to.params.id);
next();
});
},
};
</script>
// file: router/index.js
const routes = [
{
path: '/user/:id(\\d+)',
component: () => import('@/components/User.vue'),
},
];
<!-- file: App.vue -->
<template>
<h1>Main Page</h1>
<div class="nav">
<router-link :to="'/user/' + id" @click="randomId">User</router-link>
</div>
<router-view />
</template>
<script >
import { ref } from '@vue/reactivity';
export default {
name: 'App',
setup(props, context) {
const id = ref(1);
function randomInRange(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}
const randomId = () => (id.value = randomInRange(0, 100));
return {
id,
randomId,
};
},
};
</script>
注:動(dòng)態(tài)路由如果使用可變參數(shù)進(jìn)行修飾,則:
-
/user/:id*
:*
表示匹配 0 個(gè)或多個(gè)毒涧,此時(shí)的$params.id
為數(shù)組形式贮预,即{id: []}
-
/user/:id+
:+
表示匹配 1 個(gè)或多個(gè),此時(shí)的$params.id
為數(shù)組形式契讲,即{id: []}
-
/user/:id?
:?
表示匹配 0 個(gè)或 1 個(gè)仿吞,此時(shí)的$params.id
為值,即{id: 10}
注:由于動(dòng)態(tài)路由實(shí)際上映射的是同一個(gè)組件捡偏,因此唤冈,在進(jìn)行動(dòng)態(tài)路由切換時(shí),會(huì)復(fù)用該組件實(shí)例霹琼,所以組件生命周期鉤子不會(huì)被調(diào)用务傲,如果想監(jiān)聽動(dòng)態(tài)路由改變,需要手動(dòng)watch
當(dāng)前路由this.$route.params
上對(duì)應(yīng)的屬性枣申,或者使用導(dǎo)航守衛(wèi)鉤子函數(shù),比如onBeforeRouteUpdate
進(jìn)行監(jiān)聽看杭。
嵌套路由
- 嵌套路由:就是一個(gè)路由內(nèi)部可以嵌套一些子路由忠藤。
比如,/user
是一個(gè)路由楼雹,/user/one
模孩、/user/two
是/user
下的兩個(gè)子路由尖阔,所以/user
是一個(gè)嵌套了/user/one
、/user/two
的嵌套路由榨咐。
再簡單進(jìn)行理解介却,一個(gè)路由對(duì)應(yīng)一個(gè)組件,因此块茁,嵌套路由其實(shí)就是一個(gè)父組件內(nèi)部包含了多個(gè)子組件齿坷,且這些子組件也是通過路由進(jìn)行訪問。
嵌套路由的配置很簡單数焊,只需為父路由設(shè)置children
屬性即可永淌,下面以一個(gè)例子進(jìn)行驅(qū)動(dòng),闡述嵌套路由佩耳。
示例:假設(shè)現(xiàn)在有一個(gè)新聞版塊遂蛀,該版塊內(nèi)部含有兩個(gè)子版塊,分別為財(cái)經(jīng)版塊和體育版塊干厚,用代碼進(jìn)行實(shí)現(xiàn)李滴。
分析:父路由對(duì)應(yīng)組件news.vue
,其內(nèi)嵌套兩個(gè)子路由/news/finance
和/news/sports
蛮瞄,分別對(duì)應(yīng)兩個(gè)組件Finance.vue
和Sports.vue
所坯。
嵌套路由搭建步驟如下:
-
首先創(chuàng)建所有對(duì)應(yīng)組件:
<!-- file: components/nested_router/Finance.vue --> <template> <h3>Finance Component</h3> </template> <!-- file: components/nested_router/Sports.vue --> <template> <h3>Sports Component</h3> </template> <!-- file: components/nested_router/News.vue --> <template> <h1>News Component</h1> <div class="nav"> <router-link to="/news/finance">Finance</router-link> <router-link to="/news/sports">Sports</router-link> </div> <router-view /> </template> <style scoped> /* router-link 最終會(huì)被轉(zhuǎn)換為 a 標(biāo)簽 */ .nav a { margin-left: 10px; } </style>
News.vue
由于是父組件,因此其內(nèi)部包含router-view
標(biāo)簽用于展示嵌套子路由頁面組件裕坊。 -
配置路由信息:
// file: ./router/nested.js import { createRouter, createWebHistory } from 'vue-router'; const routes = [ { path: '/news', component: () => import('@/components/nested_router/News.vue'), // 配置嵌套路由 children: [ { // /news 重定向到 /news/finance path: '/news', redirect: '/news/finance', }, { // /news/finace path: 'finance', component: () => import('@/components/nested_router/Finance.vue'), }, { // /news/sports path: 'sports', component: () => import('@/components/nested_router/Sports.vue'), }, ], }, ]; export default createRouter({ // 使用 History 模式 history: createWebHistory(), routes, });
-
主頁面添加展示
/news
映射的組件New.vue
:// file: main.js import { createApp } from 'vue'; import App from './App.vue'; import router from './router/nested.js'; const app = createApp(App); app.use(router); app.mount('#app'); <!-- file: App.vue --> <template> <h1>Main Page</h1> <router-link to="/news">News</router-link> <!-- 路由出口:匹配組件最終被渲染位置 --> <router-view /> </template>
編程式導(dǎo)航
前面我們都是通過<router-link>
標(biāo)簽實(shí)現(xiàn)導(dǎo)航鏈接功能包竹,實(shí)際上當(dāng)我們點(diǎn)擊<router-link>
時(shí),其內(nèi)部會(huì)調(diào)用Router#push
方法實(shí)現(xiàn)真正的路由導(dǎo)航籍凝,因此周瞎,我們也可以直接通過編程方式,即調(diào)用Router
相關(guān)方式饵蒂,手動(dòng)實(shí)現(xiàn)導(dǎo)航跳轉(zhuǎn)声诸。
Vue Router 主要提供了以下幾個(gè)方法供我們實(shí)現(xiàn)路由跳轉(zhuǎn):
-
Router#push
:該方法會(huì)向歷史堆棧添加一個(gè)新記錄,可供我們導(dǎo)航到指定的 URL退盯。Router#push
是<router-link>
底層默認(rèn)調(diào)用的導(dǎo)航方法:聲明式 編程式 <router-link :to="...">
router.push(...)
Router#push
支持多種參數(shù)類型彼乌,其常見調(diào)用方式如下所示:// 字符串路徑 router.push('/users/eduardo') // 帶有路徑的對(duì)象 router.push({ path: '/users/eduardo' }) // 命名的路由,并加上參數(shù)渊迁,讓路由建立 url router.push({ name: 'user', params: { username: 'eduardo' } }) // 帶查詢參數(shù)慰照,結(jié)果是 /register?plan=private router.push({ path: '/register', query: { plan: 'private' } }) // 帶 hash,結(jié)果是 /about#team router.push({ path: '/about', hash: '#team' })
注:
<router-link>
中的to
屬性與Router#push
方法接收的參數(shù)類型一致琉朽,所以上述配置也適用于to
屬性毒租。注:如果同時(shí)提供了
path
和params
,則params
會(huì)被忽略箱叁。建議使用name
屬性替換path
墅垮,避免潛在問題:router.push({ name: 'Me' });
-
Router#replace
:該方法同樣支持路由導(dǎo)航惕医,但是與Router#push
不同的是,該方法不會(huì)向歷史堆棧中添加導(dǎo)航記錄算色,而是直接替換當(dāng)前導(dǎo)航記錄抬伺。
一般只有當(dāng)明確禁止跳轉(zhuǎn)回前一個(gè)路由時(shí),才會(huì)使用該方法灾梦。<router-link>
可通過添加replace
屬性峡钓,使能Router#replace
:聲明式 編程式 <router-link :to="..." replace>
router.replace(...)
Router#push
也可以實(shí)現(xiàn)replace
功能,只需為路由添加replace: true
屬性:router.push({ path: '/home', replace: true }) // 相當(dāng)于 router.replace({ path: '/home' })
-
Router#go
:該方法可以橫跨跳轉(zhuǎn)歷史堆棧:// 向前移動(dòng)一條記錄斥废,與 router.forward() 相同 router.go(1) // 返回一條記錄椒楣,與router.back() 相同 router.go(-1) // 前進(jìn) 3 條記錄 router.go(3) // 如果沒有那么多記錄,靜默失敗 router.go(-100) router.go(100)
下面還是通過一個(gè)示例驅(qū)動(dòng)進(jìn)行講解牡肉。
例子:比如主頁有兩個(gè)按鈕捧灰,要求點(diǎn)擊兩個(gè)按鈕顯示Home.vue
和Me.vue
兩個(gè)頁面。
思路:/home
路由映射組件Home.vue
统锤,/me
路由映射組件Me.vue
毛俏,然后為按鈕添加點(diǎn)擊事件,通過調(diào)用Router
相關(guān)方式實(shí)現(xiàn)路由跳轉(zhuǎn)饲窿。
具體步驟如下:
創(chuàng)建路由頁面
Home.vue
和Me.vue
煌寇。內(nèi)容參考上文-
配置路由信息:
// file: router/index.js import { createRouter, createWebHistory } from 'vue-router'; export default createRouter({ history: createWebHistory(), routes: [ { name: 'Home', path: '/home', component: () => import('@/components/Home.vue'), }, { name: 'Me', path: '/me', component: () => import('@/components/Me.vue'), }, ], });
-
加載 Vue Router 并配置主頁面:
// file: main.js import { createApp } from 'vue'; import App from './App.vue'; import router from './router/index.js'; const app = createApp(App); // 裝載 Vue Router 實(shí)例,確保整個(gè) Vue 應(yīng)用全局支持路由 app.use(router); app.mount('#app'); <!-- file: App.vue --> <template> <h1>Main Page</h1> <div class="nav"> <button @click="nav2Home">Home</button> <button @click="nav2Me">Me</button> </div> <router-view /> </template> <script setup> import { useRouter } from "vue-router" const router = useRouter(); const nav2Home = () => router.push('/home'); const nav2Me = () => router.push('/me'); </script> <style scoped> .nav button { margin-left: 10px; } </style>
運(yùn)行結(jié)果如下圖所示:
命名路由
前面很多處都提到了 命名路由逾雄,其實(shí)就是為路由配置一個(gè)name
屬性:
const routes = [
{
name: 'user', # 命名路由
path: '/user/:username',
component: User
}
]
使用命名路由阀溶,除了避免了手動(dòng)硬編碼 URL 外,最大的好處就是它會(huì)對(duì)params
屬性自動(dòng)進(jìn)行編碼/解碼鸦泳。
具體使用命名路由時(shí)银锻,只需為<router-link>
的to
屬性指定一個(gè)命名對(duì)象即可:
<router-link :to="{ name: 'user', params: { username: 'erina' }}"> User </router-link>
命名視圖
一個(gè)<router-view>
只能顯示一個(gè)組件頁面,即使是嵌套路由做鹰,同一時(shí)刻也只是顯示一個(gè)組件頁面击纬。但是如果一個(gè)路由需要同時(shí)顯示兩個(gè)及以上組件頁面,此時(shí)就需要同時(shí)提供多個(gè)<router-view>
钾麸,并且為<router-view>
設(shè)置相應(yīng)名稱更振,路由配置時(shí)會(huì)指定相應(yīng)組件顯示到對(duì)應(yīng)名稱的<router-view>
上,這種具備名稱的的<router-view>
稱之為 命名視圖饭尝。
只需為<router-view>
設(shè)置name
屬性肯腕,即為 命名視圖:
<router-view name="sidebar" />
注:實(shí)際上,所有的<router-view>
都是命名視圖钥平,未配置name
屬性時(shí)乎芳,其默認(rèn)名為default
蘸际。
舉個(gè)例子:比如現(xiàn)在主頁上有兩個(gè)組件頁面:導(dǎo)航欄sidebar
和主區(qū)域main
取刃,同時(shí)呈現(xiàn)在主頁上,要求使用命名視圖完成陌选。
思路:主頁需要配置兩個(gè)命名視圖的<router-view>
睡汹,然后路由配置時(shí)肴甸,指定相應(yīng)組件顯示到對(duì)應(yīng)的命名視圖上即可。
具體步驟如下:
-
創(chuàng)建導(dǎo)航欄組件和主區(qū)域組件:
<!-- components/named_view/SideBar.vue --> <template> <nav> <u> <li>Nav 1</li> <li>Nav 2</li> </u> </nav> </template> <style scoped> nav { background-color: green; } </style> <!-- components/named_view/Main.vue --> <template> <p>Main Content</p> </template> <style scoped> p { background-color: yellow; } </style>
-
配置路由信息:根路由需要配置兩個(gè)子組件囚巴,并指定各自要顯示到的命名視圖:
// file: router/index.js import { createRouter, createWebHistory } from 'vue-router'; export default createRouter({ history: createWebHistory(), routes: [ { path: '/', // 主頁面同時(shí)顯示多個(gè)路由映射組件 components: { // Main.vue 顯示到 default 視圖上 default: () => import('@/components/named_view/Main.vue'), // SideBar.vue 顯示到 sidebar 視圖上 sidebar: () => import('@/components/named_view/SideBar.vue'), }, }, ], });
-
主頁配置兩個(gè)命名視圖原在,分別渲染對(duì)應(yīng)的組件:
// file: main.js 參考上文 <!-- file: App.vue --> <template> <h1>Main Page</h1> <!-- 點(diǎn)擊跳轉(zhuǎn)到根路由 --> <router-link to="/">Main</router-link> <router-view name="sidebar" /> <router-view /> </template>
效果如下:
重定向
- 重定向:就是當(dāng)訪問一個(gè)路由時(shí),會(huì)被攔截并重新導(dǎo)航到另一個(gè)路由中彤叉。
重定向只需通過配置$route
即可庶柿,Vue Router 大致提供如下幾種類型重定向配置:
-
path
:直接配置重定向路徑:// 將 /home 重定向到 / const routes = [{ path: '/home', redirect: '/' }]
-
命名路由:命名路由本質(zhì)是一個(gè)路由,因此也可直接命名路由秽浇,最終重定向到該命名路由對(duì)應(yīng)的
path
:// 將 /home 重定向到命名路由 homepage const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
-
函數(shù):可以將
redirect
設(shè)置會(huì)一個(gè)函數(shù)浮庐,動(dòng)態(tài)返回重定向地址信息:const routes = [ { path: '/a', redirect: (to) => { // 參數(shù) to 是源路由地址實(shí)例對(duì)象 const { hash, params, query } = to; if (query.to === 'search') { // 返回一個(gè)路由映射配置信息 return { path: '/search', query: { q: params.searchText }}; } if (params.id) { // 返回動(dòng)態(tài)路由 return '/with-params/:id'; } else { // 返回路由地址 return '/bar'; } }, }, ];
別名
- 別名:Vue Router 中,別名 指的是一個(gè)路由對(duì)應(yīng)兩個(gè)路徑柬焕,即訪問這兩個(gè)路徑都能跳轉(zhuǎn)到該路由审残。
比如對(duì)于下面的配置:
const routes = [
{
path: '/user_one', // 路徑
alias: '/user_1', // 別名
component: User,
},
];
其實(shí)就是為/user_one
設(shè)置了一個(gè)別名/user_1
,此時(shí)訪問/user_one
或/user_1
都可以導(dǎo)航到User.vue
組件斑举。
注:如果想同時(shí)指定多個(gè)別名搅轿,則需進(jìn)行如下配置:
const routes = [
{
path: '/user_one',
component: User,
alias: ['/user_1', '/user_yi'], // 使用數(shù)組即可
},
];
路由組件傳參
- 路由組件傳參:其實(shí)就是跳轉(zhuǎn)時(shí),給路由映射組件傳遞參數(shù)富玷。
前面內(nèi)容璧坟,在模板中,我們都是通過$route
直接獲取路由信息:
<!-- file: components/User.vue -->
<template>
<h1>User Component: {{ $route.params.name }}</h1>
</template>
// file: router/index.js
const routes = [
{
path: '/user/:name',
component: () => import('@/components/User.vue'),
},
];
這種做法其實(shí)緊耦合了路由與組件赎懦,對(duì)組件復(fù)用性產(chǎn)生消極影響雀鹃。
一個(gè)更靈活的解決辦法是通過為組件動(dòng)態(tài)傳遞參數(shù),Vue Router 大致提供了如下幾種路由參數(shù)傳遞方法:
-
布爾模式:為路由地址映射配置一個(gè)
props: true
铲敛,此時(shí)route.params
會(huì)被傳遞給組件的props
:比如褐澎,上面的例子使用參數(shù)傳遞,可修改為如下:
// file: router/index.js const routes = [ { path: '/user/:name', component: () => import('@/components/User.vue'), props: true, // 使能路由參數(shù)傳遞 }, ]; <!-- file: components/User.vue --> <template> <!-- 顯示傳遞的參數(shù) --> <h1>User Component: {{ name }}</h1> </template> <script> export default { name: 'User', props: { name: String, // 接收傳遞的參數(shù) }, }; </script>
-
命名視圖:對(duì)于有命名視圖的路由伐蒋,必須顯示為每個(gè)命名視圖定義
props
配置:const routes = [ { path: '/user/:name', components: { default: User, sidebar: Sidebar }, // default 視圖使能參數(shù)傳遞工三,sidebar 視圖關(guān)閉參數(shù)傳遞 props: { default: true, sidebar: false } } ]
-
對(duì)象模式:將
props
設(shè)置為對(duì)象,直接傳遞該對(duì)象給組件:const routes = [ { path: '/user/:name', components: { default: User, sidebar: Sidebar }, // 直接傳遞對(duì)象 props: { name: 'Whyn' } } ]
注:對(duì)象模式此時(shí)直接傳遞
props
對(duì)象先鱼,與路由params
沒有關(guān)系俭正。當(dāng)我們需要傳遞靜態(tài)內(nèi)容給到路由組件時(shí),對(duì)象模式就很合適焙畔。 -
函數(shù)模式:可以將
props
設(shè)置為一個(gè)函數(shù)掸读,該函數(shù)參數(shù)是路由地址信息$route
,因此可以在函數(shù)內(nèi)部提取當(dāng)前路由相關(guān)信息,結(jié)合自己一些靜態(tài)內(nèi)容儿惫,組合進(jìn)行設(shè)置澡罚,更加靈活:const routes = [ { path: '/user', component: () => import('@/components/User.vue'), props: (route) => ({ query: route.query.name }), }, ];
上述配置中,比如此時(shí)我們跳轉(zhuǎn)到
/user?name=Whyn
時(shí)肾请,則會(huì)傳遞{ query: 'Whyn' }
給到User.vue
組件:<!-- file: components/User.vue --> <template> <h1>User Component: {{ query }}</h1> </template> <script> export default { name: 'User', props: { query: String, }, }; </script>
導(dǎo)航守衛(wèi)
- 導(dǎo)航守衛(wèi):所謂 導(dǎo)航守衛(wèi)留搔,其實(shí)就是一些路由鉤子,在進(jìn)行路由跳轉(zhuǎn)或取消時(shí)铛铁,會(huì)觸發(fā)相應(yīng)鉤子隔显,我們可以在這些鉤子中,檢測(cè)路由跳轉(zhuǎn)及獲取相關(guān)數(shù)據(jù)饵逐,植入我們自己的操作括眠。
Vue Router 總共提供了如下三種類型導(dǎo)航守衛(wèi):
-
全局守衛(wèi):可以監(jiān)控所有路由跳轉(zhuǎn)的導(dǎo)航守衛(wèi)。具體可在細(xì)分為如下三種類型:
-
全局前置守衛(wèi):當(dāng)觸發(fā)一個(gè)導(dǎo)航跳轉(zhuǎn)時(shí)倍权,全局前置守衛(wèi)會(huì)被觸發(fā)調(diào)用(多個(gè)全局前置守衛(wèi)會(huì)按照創(chuàng)建順序依次回調(diào))掷豺。
可通過
Router#beforeEach
注冊(cè)一個(gè)全局前置守衛(wèi):const router = createRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
全局前置守衛(wèi)是異步解析執(zhí)行的,即回調(diào)函數(shù)與
async
和Promise
工作方式一樣:// 上述代碼相當(dāng)于如下 router.beforeEach(async (to, from, next) => { // canUserAccess() 返回 `true` 或 `false` return await canUserAccess(to) })
全局前置守衛(wèi)每個(gè)守衛(wèi)方法可接受三個(gè)參數(shù):
-
to
:表示即將要進(jìn)入的路由目標(biāo) -
from
:當(dāng)前導(dǎo)航正要離開的路由對(duì)象 -
next
:可選參數(shù)账锹,用于觸發(fā)并傳遞額外數(shù)據(jù)給下一個(gè)路由對(duì)象:
router.beforeEach((to, from, next) => { // 進(jìn)行管道的下一個(gè)鉤子 next(); // 中斷當(dāng)前導(dǎo)航萌业,URL 重置為 from 路由路徑 next(false); // 跳轉(zhuǎn)到指定路由 /home next('/home'); // 同 next('/home') next( {path: '/home'} ); // 如果 next 參數(shù)是一個(gè) Error 實(shí)例,則導(dǎo)航會(huì)被終止奸柬, // 且參數(shù) error 會(huì)被傳遞給 Router#onError() 回調(diào) next(error); })
全局前置守衛(wèi)每個(gè)守衛(wèi)方法返回值有如下幾種可選:
-
true
|undefined
:返回true
或undefined
生年,表示導(dǎo)航有效,并自動(dòng)調(diào)用下一個(gè)導(dǎo)航守衛(wèi):router.beforeEach((to, from) => { // 沒有 return廓奕,則表示 return undefined return false; });
注:如果顯示聲明了第三個(gè)參數(shù)
next
抱婉,則必須手動(dòng)調(diào)用next
進(jìn)行跳轉(zhuǎn):router.beforeEach((to, from, next) => { // false next(true); // 或者 undefined next(); });
-
false
:表示取消導(dǎo)航跳轉(zhuǎn),即當(dāng)前 URL 重置到from
路由地址router.beforeEach((to, from) => { // 取消導(dǎo)航桌粉,停留在 from 路由頁面 return false; });
-
一個(gè)路由地址:指定導(dǎo)航跳轉(zhuǎn)地址蒸绩,相當(dāng)于調(diào)用了
Router#push
:const routes = [ // ... { path: '/user', component: () => import('@/components/User.vue') }, ], router.beforeEach((to, from) => { if (to.path === '/user') { return true; return '/user'; });
-
Error
:拋出異常,此時(shí)會(huì)取消導(dǎo)航并回調(diào)Router#onError
全局錯(cuò)誤鉤子:router.beforeEach((to, from, next) => { // 手動(dòng)拋異常铃肯,會(huì)被 onError 捕獲到 throw 'error occured!!'; }); router.onError((error, to, from) => { console.log(error, to, from); });
-
-
全局解析守衛(wèi):全局解析守衛(wèi)與全局前置守衛(wèi)類似患亿,都是在 每次導(dǎo)航 時(shí)都會(huì)被觸發(fā),但是全局解析守衛(wèi)會(huì)確保在 所有組件內(nèi)守衛(wèi)和異步路由組件被解析后押逼,才會(huì)被正確調(diào)用步藕。
可通過
Router#beforeResolve
方法注冊(cè)一個(gè)全局解析守衛(wèi):router.beforeResolve((to, from, next) => { console.log('Router beforeResolve'); });
Router#beforeResolve
是獲取數(shù)據(jù)或執(zhí)行任何其他操作(如果用戶無法進(jìn)入頁面時(shí)你希望避免執(zhí)行的操作)的理想位置。 -
全局后置鉤子:全局后置鉤子是在導(dǎo)航被確認(rèn)后挑格,才進(jìn)行調(diào)用咙冗,因此,該鉤子不會(huì)攜帶
next
函數(shù)漂彤,也不會(huì)改變導(dǎo)航狀態(tài)雾消。可通過
Router#afterEach
注冊(cè)一個(gè)全局后置鉤子:router.afterEach((to, from, failure) => { console.log('Router afterEach'); });
Router#afterEach
對(duì)于分析灾搏、更改頁面標(biāo)題、聲明頁面等輔助功能以及許多其他事情都很有用立润。
-
-
路由獨(dú)享守衛(wèi):如果只關(guān)注特定路由導(dǎo)航跳轉(zhuǎn)事件狂窑,那么只需為相應(yīng)路由添加導(dǎo)航守衛(wèi)即可。
只需在路由配置上定義
beforeEnter
函數(shù)即可注冊(cè)一個(gè)路由獨(dú)享導(dǎo)航守衛(wèi):const routes = [ { path: '/home', component: () => import('@/components/Home.vue'), // 路由獨(dú)享守衛(wèi) beforeEnter: (to, from, next) => { console.log('Home: Route beforeEnter'); next(); }, // 支持傳遞多個(gè)鉤子函數(shù) // beforeEnter: [(..)=>{..}, (..)=>{..}], }, ];
beforeEnter
只在進(jìn)入路由時(shí)觸發(fā)范删,更改params
蕾域、query
或hash
時(shí),不會(huì)觸發(fā)beforeEnter
到旦。 -
組件內(nèi)守衛(wèi):一個(gè)路由最終映射為一個(gè)組件,因此我們也可以在組件內(nèi)定義導(dǎo)航守衛(wèi)巨缘,捕獲路由跳轉(zhuǎn)導(dǎo)航相關(guān)信息添忘。
組件內(nèi)守衛(wèi)在 Options API 中可通過為組件添加
beforeRouteEnter
、beforeRouteUpdate
和beforeRouteLeave
函數(shù)進(jìn)行注冊(cè):const UserDetails = { template: `...`, beforeRouteEnter(to, from) { // 在渲染該組件的對(duì)應(yīng)路由被驗(yàn)證前調(diào)用 // 不能獲取組件實(shí)例 `this` 若锁! // 因?yàn)楫?dāng)守衛(wèi)執(zhí)行時(shí)搁骑,組件實(shí)例還沒被創(chuàng)建! }, beforeRouteUpdate(to, from) { // 在當(dāng)前路由改變又固,但是該組件被復(fù)用時(shí)調(diào)用 // 舉例來說仲器,對(duì)于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 `/users/:id`,在 `/users/1` 和 `/users/2` 之間跳轉(zhuǎn)的時(shí)候仰冠, // 由于會(huì)渲染同樣的 `UserDetails` 組件乏冀,因此組件實(shí)例會(huì)被復(fù)用。而這個(gè)鉤子就會(huì)在這個(gè)情況下被調(diào)用洋只。 // 因?yàn)樵谶@種情況發(fā)生的時(shí)候辆沦,組件已經(jīng)掛載好了,導(dǎo)航守衛(wèi)可以訪問組件實(shí)例 `this` }, beforeRouteLeave(to, from) { // 在導(dǎo)航離開渲染該組件的對(duì)應(yīng)路由時(shí)調(diào)用 // 與 `beforeRouteUpdate` 一樣识虚,它可以訪問組件實(shí)例 `this` }, }
組件內(nèi)守衛(wèi)在 Composition API 中可通過在
setup
函數(shù)中定義onBeforeRouteUpdate
和onBeforeRouteLeave
分別注冊(cè) update 和 leave 守衛(wèi):<script> import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'; export default { name: 'Home', setup(props, context) { console.log('setup = onBeforeRouteEnter'); // update 守衛(wèi)確認(rèn)導(dǎo)航跳轉(zhuǎn)肢扯,因此沒有 next 參數(shù) onBeforeRouteUpdate((fto, from) => { console.log('onBeforeRouteUpdate'); }); // leave 守衛(wèi)同樣沒有 next 參數(shù) onBeforeRouteLeave((to, from) => { console.log('onBeforeRouteLeave'); }); }, }; </script>
Composition API 中,
setup
等于beforeRouteEnter
担锤,在路由進(jìn)入時(shí)被觸發(fā)蔚晨;onBeforeRouteUpdate
在第一次路由進(jìn)入時(shí),不會(huì)被觸發(fā)肛循,只有在該路由重復(fù)進(jìn)入铭腕,組件被復(fù)用時(shí)才會(huì)被觸發(fā)(比如params
的改變);onBeforeRouteLeave
在離開當(dāng)前路由時(shí)會(huì)被觸發(fā)育拨。
最后谨履,全局導(dǎo)航守衛(wèi)、路由獨(dú)享守衛(wèi)和組件內(nèi)守衛(wèi)完整的導(dǎo)航解析流程如下圖所示:
注:流程圖來源于網(wǎng)上熬丧,侵刪笋粟。
附錄
- 本篇博文 Demo 源碼可查看:vue-router4.x-demo