Vue Router
路由概念
前端路由通过 URL 中的 hash(#)或 HTML5 History 实现单页应用(SPA)的页面切换,无需重新加载页面。
Vue2 路由
安装与配置
js
// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'
Vue.use(Router)
export default new Router({
mode: 'history', // 路由模式:hash / history / abstract
base: '/', // 基础路径
linkActiveClass: 'active', // 路由匹配时的样式类
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue') // 路由懒加载
}
]
})// src/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'
Vue.use(Router)
export default new Router({
mode: 'history', // 路由模式:hash / history / abstract
base: '/', // 基础路径
linkActiveClass: 'active', // 路由匹配时的样式类
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue') // 路由懒加载
}
]
})js
// main.js
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')// main.js
import router from './router'
new Vue({
router,
render: h => h(App)
}).$mount('#app')路由显示
vue
<template>
<div id="app">
<!-- 导航链接 -->
<router-link to="/">首页</router-link>
<router-link :to="{ name: 'About' }">关于</router-link>
<router-link to="/user/123" active-class="active">用户</router-link>
<!-- 路由出口 -->
<router-view />
</div>
</template><template>
<div id="app">
<!-- 导航链接 -->
<router-link to="/">首页</router-link>
<router-link :to="{ name: 'About' }">关于</router-link>
<router-link to="/user/123" active-class="active">用户</router-link>
<!-- 路由出口 -->
<router-view />
</div>
</template>动态路由匹配
js
const routes = [
// 动态参数
{ path: '/user/:id', component: User },
// 可选参数(加 ?)
{ path: '/shop/:type/:id?', component: Shop }
]const routes = [
// 动态参数
{ path: '/user/:id', component: User },
// 可选参数(加 ?)
{ path: '/shop/:type/:id?', component: Shop }
]vue
<template>
<div>
<!-- 通过 $route.params 获取参数 -->
<p>用户ID:{{ $route.params.id }}</p>
<p>查询参数:{{ $route.query.page }}</p>
</div>
</template><template>
<div>
<!-- 通过 $route.params 获取参数 -->
<p>用户ID:{{ $route.params.id }}</p>
<p>查询参数:{{ $route.query.page }}</p>
</div>
</template>vue
<template>
<!-- 传参方式 -->
<router-link :to="{ name: 'User', params: { id: 123 } }">用户</router-link>
<router-link :to="{ path: '/user', query: { id: 123 } }">用户</router-link>
</template><template>
<!-- 传参方式 -->
<router-link :to="{ name: 'User', params: { id: 123 } }">用户</router-link>
<router-link :to="{ path: '/user', query: { id: 123 } }">用户</router-link>
</template>嵌套路由
js
const routes = [
{
path: '/account',
component: Account,
redirect: '/account/login', // 重定向
children: [
{ path: 'login', component: Login }, // 访问 /account/login
{ path: 'register', component: Register } // 访问 /account/register
// 注意子路由 path 不加 /
]
}
]const routes = [
{
path: '/account',
component: Account,
redirect: '/account/login', // 重定向
children: [
{ path: 'login', component: Login }, // 访问 /account/login
{ path: 'register', component: Register } // 访问 /account/register
// 注意子路由 path 不加 /
]
}
]vue
<template>
<!-- Account.vue — 父组件需要包含 router-view -->
<div>
<h2>账户管理</h2>
<router-view />
</div>
</template><template>
<!-- Account.vue — 父组件需要包含 router-view -->
<div>
<h2>账户管理</h2>
<router-view />
</div>
</template>编程式导航
js
// 跳转(带历史记录)
this.$router.push('/home')
this.$router.push({ name: 'User', params: { id: 123 } })
this.$router.push({ path: '/user', query: { page: 1 } })
// 替换(不带历史记录)
this.$router.replace('/home')
// 前进/后退
this.$router.go(1) // 前进
this.$router.go(-1) // 后退// 跳转(带历史记录)
this.$router.push('/home')
this.$router.push({ name: 'User', params: { id: 123 } })
this.$router.push({ path: '/user', query: { page: 1 } })
// 替换(不带历史记录)
this.$router.replace('/home')
// 前进/后退
this.$router.go(1) // 前进
this.$router.go(-1) // 后退重复点击路由报错处理(Vue2):
js
// router/index.js
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}// router/index.js
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}命名视图
vue
<template>
<!-- 在一个路由下显示多个组件 -->
<router-view name="sidebar" />
<router-view name="main" />
<router-view /> <!-- 默认:name="default" -->
</template><template>
<!-- 在一个路由下显示多个组件 -->
<router-view name="sidebar" />
<router-view name="main" />
<router-view /> <!-- 默认:name="default" -->
</template>js
const routes = [
{
path: '/dashboard',
components: {
default: Dashboard,
sidebar: Sidebar,
main: MainContent
}
}
]const routes = [
{
path: '/dashboard',
components: {
default: Dashboard,
sidebar: Sidebar,
main: MainContent
}
}
]路由守卫
全局前置守卫
js
const router = new Router({ routes })
router.beforeEach((to, from, next) => {
// to — 目标路由对象
// from — 当前路由对象
// next() — 放行
// next(false) — 中断导航
// next('/login') — 重定向
// next({ name: 'Login' }) — 重定向
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next({ name: 'Login' })
} else {
next()
}
})const router = new Router({ routes })
router.beforeEach((to, from, next) => {
// to — 目标路由对象
// from — 当前路由对象
// next() — 放行
// next(false) — 中断导航
// next('/login') — 重定向
// next({ name: 'Login' }) — 重定向
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next({ name: 'Login' })
} else {
next()
}
})全局后置守卫
js
router.afterEach((to, from) => {
// 跳转后操作,没有 next
document.title = to.meta.title || '默认标题'
})router.afterEach((to, from) => {
// 跳转后操作,没有 next
document.title = to.meta.title || '默认标题'
})路由独享守卫
js
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter(to, from, next) {
if (localStorage.getItem('role') !== 'admin') {
next('/')
} else {
next()
}
}
}
]const routes = [
{
path: '/admin',
component: Admin,
beforeEnter(to, from, next) {
if (localStorage.getItem('role') !== 'admin') {
next('/')
} else {
next()
}
}
}
]组件内守卫
js
export default {
beforeRouteEnter(to, from, next) {
// 进入组件前,不能访问 this(组件未创建)
// 可以通过回调访问实例
next(vm => {
vm.loadData()
})
},
beforeRouteUpdate(to, from, next) {
// 路由参数变化时(如 /user/1 → /user/2)
// 可以访问 this
this.fetchUser(to.params.id)
next()
},
beforeRouteLeave(to, from, next) {
// 离开组件前
const answer = window.confirm('确认离开?')
if (answer) next()
else next(false)
}
}export default {
beforeRouteEnter(to, from, next) {
// 进入组件前,不能访问 this(组件未创建)
// 可以通过回调访问实例
next(vm => {
vm.loadData()
})
},
beforeRouteUpdate(to, from, next) {
// 路由参数变化时(如 /user/1 → /user/2)
// 可以访问 this
this.fetchUser(to.params.id)
next()
},
beforeRouteLeave(to, from, next) {
// 离开组件前
const answer = window.confirm('确认离开?')
if (answer) next()
else next(false)
}
}路由元信息
js
const routes = [
{
path: '/admin',
component: Admin,
meta: {
title: '管理后台',
requiresAuth: true,
roles: ['admin', 'editor']
}
}
]const routes = [
{
path: '/admin',
component: Admin,
meta: {
title: '管理后台',
requiresAuth: true,
roles: ['admin', 'editor']
}
}
]动态路由
js
// router.addRoutes — Vue2.2+ 新增动态路由
router.addRoutes([
{ path: '/new', component: NewPage }
])// router.addRoutes — Vue2.2+ 新增动态路由
router.addRoutes([
{ path: '/new', component: NewPage }
])滚动行为
js
const router = new Router({
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition // 前进/后退时保持滚动位置
}
if (to.hash) {
return { selector: to.hash } // 滚动到锚点
}
return { x: 0, y: 0 } // 滚动到顶部
}
})const router = new Router({
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition // 前进/后退时保持滚动位置
}
if (to.hash) {
return { selector: to.hash } // 滚动到锚点
}
return { x: 0, y: 0 } // 滚动到顶部
}
})Vue3 路由
安装与配置
js
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue'),
meta: { title: '关于', requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(), // createWebHashHistory / createMemoryHistory
routes
})
export default router// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue'),
meta: { title: '关于', requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(), // createWebHashHistory / createMemoryHistory
routes
})
export default routerjs
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')组合式 API — useRouter / useRoute
vue
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 访问参数
console.log(route.params.id)
console.log(route.query.page)
// 编程式导航
function goHome() {
router.push({ name: 'Home' })
}
function goUser(id) {
router.push({ name: 'User', params: { id } })
}
function goBack() {
router.go(-1)
}
</script><script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 访问参数
console.log(route.params.id)
console.log(route.query.page)
// 编程式导航
function goHome() {
router.push({ name: 'Home' })
}
function goUser(id) {
router.push({ name: 'User', params: { id } })
}
function goBack() {
router.go(-1)
}
</script>组合式导航守卫
vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 离开守卫
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('确认离开?')
if (!answer) return false
})
// 路由参数更新守卫
onBeforeRouteUpdate((to, from) => {
fetchUser(to.params.id)
})
</script><script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 离开守卫
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('确认离开?')
if (!answer) return false
})
// 路由参数更新守卫
onBeforeRouteUpdate((to, from) => {
fetchUser(to.params.id)
})
</script>路由自动引入
js
// 使用 require.context 自动注册路由(Vue2 Webpack)
const routerContext = require.context('./', true, /index\.js$/)
routerContext.keys().forEach(route => {
if (route.startsWith('./index')) return
const routerModule = routerContext(route)
routes = [...routes, ...(routerModule.default || routerModule)]
})// 使用 require.context 自动注册路由(Vue2 Webpack)
const routerContext = require.context('./', true, /index\.js$/)
routerContext.keys().forEach(route => {
if (route.startsWith('./index')) return
const routerModule = routerContext(route)
routes = [...routes, ...(routerModule.default || routerModule)]
})Vue2 vs Vue3 路由对比
| 特性 | Vue2 (vue-router 3.x) | Vue3 (vue-router 4.x) |
|---|---|---|
| 创建方式 | new Router() | createRouter() |
| 历史模式 | mode: 'history' | history: createWebHistory() |
| 获取路由 | this.$router / this.$route | useRouter() / useRoute() |
| 组件内守卫 | beforeRouteEnter 等 | onBeforeRouteLeave / onBeforeRouteUpdate |
| 动态路由 | router.addRoutes() | router.addRoute() |
| 应用挂载 | Vue.use(Router) | app.use(router) |
Cao Note