Router 路由原理详解
1. 从一个问题开始
在传统的多页应用(MPA)中,每次点击链接都会向服务器发起新的请求,页面会整体刷新。而在 Vue 构建的单页应用(SPA)中,我们可以在"不刷新页面"的情况下切换不同的视图——这背后的核心技术就是 前端路由。
// 用户点击导航
<router-link to="/about">关于我们</router-link>
// 页面没有刷新,但视图切换了 —— 这是怎么做到的?
你是否好奇过:
- URL 变了,但页面为什么没有刷新?
<router-view>是怎么知道该渲染哪个组件的?- 导航守卫是如何做到"拦截"路由跳转的?
- Vue Router 和 Vue 的响应式系统是怎么结合的?
带着这些问题,让我们一步步拆解 Vue Router 的实现原理。
2. 前端路由的两种模式
前端路由的核心思想是:监听 URL 变化,在不重新请求页面的情况下,更新视图。
Vue Router 提供了两种路由模式:Hash 模式 和 History 模式。
2.1 Hash 模式
Hash 模式利用 URL 中 # 后面的部分(即 hash)来模拟路径。
https://example.com/#/home
https://example.com/#/about
核心原理:
#后面的内容变化 不会触发浏览器向服务器发送请求- 通过监听
hashchange事件来感知 URL 变化
// Hash 模式的核心实现
window.addEventListener('hashchange', () => {
const path = window.location.hash.slice(1) // 去掉 #
// 根据 path 匹配对应的组件并渲染
renderComponent(path)
})
// 改变 hash
window.location.hash = '#/about'
2.2 History 模式
History 模式利用 HTML5 提供的 History API(pushState / replaceState)来管理 URL。
https://example.com/home
https://example.com/about
核心原理:
history.pushState()可以修改 URL 但 不会触发页面刷新- 通过监听
popstate事件感知浏览器前进/后退 - 注意:
pushState和replaceState本身 不会触发popstate事件
// History 模式的核心实现
// 改变 URL(不刷新页面)
history.pushState({ path: '/about' }, '', '/about')
// 监听浏览器前进/后退
window.addEventListener('popstate', (event) => {
const path = window.location.pathname
renderComponent(path)
})
History 模式下,如果用户直接在浏览器中输入 URL 或刷新页面,浏览器会向服务器发起真实请求。如果服务器没有对应的路由配置,就会返回 404。因此,使用 History 模式需要 服务端配合,将所有路由请求指向 index.html:
# Nginx 配置示例
location / {
try_files $uri $uri/ /index.html;
}
2.3 两种模式对比
| 特性 | Hash 模式 | History 模式 |
|---|---|---|
| URL 美观度 | 带 #,较丑 | 干净,无 # |
| 兼容性 | 支持所有浏览器 | IE10+ |
| 服务器配置 | 无需配置 | 需要配置回退路由 |
| SEO | 不利于 SEO | 对 SEO 更友好 |
| 原理 | hashchange 事件 | History API + popstate |
| 锚点功能 | 与原生锚点冲突 | 不冲突 |
3. Vue Router 整体架构
了解了前端路由的基础原理后,让我们看看 Vue Router 是如何把这些能力集成到 Vue 生态中的。
Vue Router 的核心由以下几部分组成:
- 路由匹配器(Matcher):将 URL 路径与路由配置进行匹配
- 历史管理(History):封装 Hash / History API,统一接口
- 响应式路由状态(currentRoute):与 Vue 响应式系统绑定
- 导航守卫系统:提供路由跳转的生命周期钩子
- 内置组件:
<router-view>和<router-link>
4. 路由匹配机制
4.1 路由配置与解析
Vue Router 接收一个路由配置数组,每一项描述了一条路径和对应的组件:
const routes = [
{ path: '/', component: Home },
{ path: '/user/:id', component: User },
{ path: '/about', component: About },
{
path: '/dashboard',
component: Dashboard,
children: [
{ path: 'profile', component: Profile }, // /dashboard/profile
{ path: 'settings', component: Settings }, // /dashboard/settings
],
},
]
4.2 路径匹配的核心 —— 路径转正则
Vue Router 需要把路径字符串(如 /user/:id)转换为正则表达式,以便与实际 URL 进行匹配。
简化版的路径解析过程:
// 路径 /user/:id 的解析示意
// 1. 词法分析 —— 将路径拆解为 token
const tokens = [
{ type: 'Static', value: 'user' },
{ type: 'Param', value: 'id' },
]
// 2. 根据 token 生成正则表达式
// Static token → 字面匹配
// Param token → 捕获组 ([^/]+?)
const regex = /^\/user\/([^/]+?)\/?$/
// 3. 匹配实际 URL
const match = '/user/123'.match(regex)
// match[1] = '123'
// 4. 提取参数
const params = { id: '123' }
4.3 动态路由参数
Vue Router 支持多种动态路径模式:
// 基础动态参数
{ path: '/user/:id' }
// 匹配 /user/123 → params: { id: '123' }
// 多个参数
{ path: '/user/:userId/post/:postId' }
// 匹配 /user/1/post/99 → params: { userId: '1', postId: '99' }
// 可选参数(Vue Router 4)
{ path: '/user/:id?' }
// 匹配 /user 和 /user/123
// 可重复参数(匹配多段路径)
{ path: '/files/:path+' }
// 匹配 /files/a/b/c → params: { path: ['a', 'b', 'c'] }
// 可选的可重复参数
{ path: '/files/:path*' }
// 匹配 /files 和 /files/a/b/c
// 自定义正则约束
{ path: '/user/:id(\\d+)' }
// 只匹配数字 /user/123 ✓ /user/abc ✗
4.4 路由匹配优先级
当多条路由都可能匹配同一个 URL 时,Vue Router 4 采用 评分系统 来确定优先级:
const routes = [
{ path: '/user/profile' }, // 静态路径,优先级最高
{ path: '/user/:id' }, // 动态参数,优先级次之
{ path: '/user/:id(\\d+)' }, // 有正则约束的动态参数
{ path: '/:pathMatch(.*)*' }, // 通配符,优先级最低
]
评分规则(简化):
| 路径类型 | 评分 | 示例 |
|---|---|---|
| 静态路径段 | +4 | /user |
| 带正则的动态参数 | +3 | /:id(\\d+) |
| 动态参数 | +2 | /:id |
| 通配符 | +1 | /:path(.*) |
5. 响应式路由 —— 与 Vue 的桥梁
Vue Router 之所以能在路由变化时自动更新视图,核心在于它将当前路由状态做成了 响应式数据。
5.1 Vue Router 3(Vue 2)的实现
在 Vue Router 3 中,利用了 Vue.util.defineReactive 将 _route 变为响应式属性:
// Vue Router 3 安装时的核心逻辑(简化)
class VueRouter {
init(app) {
// 监听路由变化,更新响应式属性
this.history.listen((route) => {
app._route = route
})
}
}
// install 方法中
Vue.mixin({
beforeCreate() {
if (this.$options.router) {
// 根实例:将 _route 变为响应式
Vue.util.defineReactive(this, '_route', this.$options.router.history.current)
}
},
})
// 当 _route 变化时,依赖它的 <router-view> 会自动重新渲染
5.2 Vue Router 4(Vue 3)的实现
在 Vue Router 4 中,使用了 Vue 3 的 shallowRef 来实现响应式:
// Vue Router 4 核心逻辑(简化)
import { shallowRef } from 'vue'
function createRouter(options) {
// 当前路由是一个浅层 ref
const currentRoute = shallowRef(START_LOCATION)
// 路由跳转完成后更新
function finalizeNavigation(toLocation) {
// 更新响应式路由 → 触发视图更新
currentRoute.value = toLocation
}
// 通过 provide/inject 向组件注入路由信息
const router = {
install(app) {
app.provide('router', router)
app.provide('currentRoute', currentRoute)
},
}
return router
}
5.3 为什么用 shallowRef 而不是 ref?
// ref 会深度追踪对象内部变化
const route = ref({ path: '/home', params: {}, query: {} })
// → 内部每个属性都被代理,性能开销大
// shallowRef 只追踪 .value 的替换
const route = shallowRef({ path: '/home', params: {}, query: {} })
// → 只有整体替换时才触发更新,性能更好
// → 路由对象本身是不可变的(每次导航生成新对象),所以 shallowRef 足够
6. 导航守卫 —— 路由的生命周期
导航守卫是 Vue Router 最强大的功能之一,它允许你在路由跳转的各个阶段插入逻辑,比如鉴权、数据预加载、离开确认等。
6.1 完整的导航解析流程
6.2 各类守卫的使用
全局守卫:
const router = createRouter({ ... })
// 全局前置守卫 —— 最常用,用于鉴权
router.beforeEach((to, from) => {
// to: 即将进入的路由
// from: 即将离开的路由
if (to.meta.requiresAuth && !isAuthenticated()) {
// 未登录,重定向到登录页
return { name: 'Login' }
}
// 返回 undefined 或 true 表示放行
})
// 全局解析守卫 —— 在所有组件内守卫和异步路由组件被解析后调用
router.beforeResolve(async (to) => {
if (to.meta.requiresData) {
await fetchData(to.params.id)
}
})
// 全局后置钩子 —— 不能改变导航,适合做分析、页面标题等
router.afterEach((to, from) => {
document.title = to.meta.title || '默认标题'
})
路由独享守卫:
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
if (!isAdmin()) {
return { name: 'Forbidden' }
}
},
},
]
组件内守卫:
// Options API
export default {
beforeRouteEnter(to, from, next) {
// 组件实例还未创建,无法访问 this
next((vm) => {
// 通过回调访问组件实例
vm.loadData()
})
},
beforeRouteUpdate(to, from) {
// 路由参数变化但复用同一组件时触发
// 如 /user/1 → /user/2
this.userId = to.params.id
},
beforeRouteLeave(to, from) {
// 离开前确认
if (this.hasUnsavedChanges) {
return confirm('确定要离开吗?未保存的修改将丢失。')
}
},
}
// Composition API(Vue Router 4)
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
export default {
setup() {
onBeforeRouteUpdate((to, from) => {
// 路由参数变化时
})
onBeforeRouteLeave((to, from) => {
// 离开当前路由时
})
},
}
6.3 导航守卫的实现原理
导航守卫本质上是一个 异步队列执行器,将所有守卫按顺序收集到队列中,依次执行:
// 简化版导航守卫执行原理
function navigate(to, from) {
// 1. 收集所有需要执行的守卫
const guards = []
.concat(extractLeaveGuards(from)) // beforeRouteLeave
.concat(router.beforeEach.handlers) // 全局 beforeEach
.concat(extractUpdateGuards(to)) // beforeRouteUpdate
.concat(to.matched.map((r) => r.beforeEnter)) // 路由独享
.concat(resolveAsyncComponents(to)) // 解析异步组件
// 2. 按顺序执行守卫队列
return runGuardQueue(guards)
}
// 顺序执行守卫队列
function runGuardQueue(guards) {
return guards.reduce(
(promise, guard) =>
promise.then(() => {
return new Promise((resolve, reject) => {
// 执行守卫,等待 next() 被调用
guard(to, from, (nextArg) => {
if (nextArg === false) {
reject(new NavigationAborted())
} else if (typeof nextArg === 'string' || typeof nextArg === 'object') {
reject(new NavigationRedirect(nextArg))
} else {
resolve()
}
})
})
}),
Promise.resolve()
)
}
7. RouterView 与 RouterLink 的实现
7.1 RouterView 组件
<router-view> 的核心职责是:根据当前路由,渲染对应的组件。
简化版 RouterView 实现:
// Vue Router 4 中 RouterView 的简化实现
const RouterView = defineComponent({
name: 'RouterView',
props: {
name: { type: String, default: 'default' },
},
setup(props, { slots }) {
// 注入当前路由(响应式)
const injectedRoute = inject('currentRoute')
// 当前 RouterView 的嵌套深度
const depth = inject('routerViewDepth', 0)
provide('routerViewDepth', depth + 1)
return () => {
const route = injectedRoute.value
// 根据深度获取匹配的路由记录
const matchedRoute = route.matched[depth]
if (!matchedRoute) {
return slots.default?.()
}
const component = matchedRoute.components[props.name]
return h(component, { route })
}
},
})
嵌套路由的 depth 机制:
// 路由配置
const routes = [
{
path: '/parent',
component: Parent, // 匹配 matched[0] → 外层 RouterView (depth=0)
children: [
{
path: 'child',
component: Child, // 匹配 matched[1] → 内层 RouterView (depth=1)
},
],
},
]
<!-- App.vue -->
<router-view /> <!-- depth = 0,渲染 Parent -->
<!-- Parent.vue -->
<div>
<h1>父组件</h1>
<router-view /> <!-- depth = 1,渲染 Child -->
</div>
7.2 RouterLink 组件
<router-link> 会渲染为一个 <a> 标签,但它拦截了默认的点击行为,转而使用 router.push 进行导航:
// 简化版 RouterLink 实现
const RouterLink = defineComponent({
name: 'RouterLink',
props: {
to: { type: [String, Object], required: true },
replace: Boolean,
},
setup(props, { slots }) {
const router = inject('router')
const currentRoute = inject('currentRoute')
// 解析目标路由
const route = computed(() => router.resolve(props.to))
// 判断是否激活
const isActive = computed(() =>
currentRoute.value.path.startsWith(route.value.path)
)
const isExactActive = computed(() =>
currentRoute.value.path === route.value.path
)
function navigate(e) {
e.preventDefault()
if (props.replace) {
router.replace(props.to)
} else {
router.push(props.to)
}
}
return () =>
h(
'a',
{
href: route.value.href,
onClick: navigate,
class: {
'router-link-active': isActive.value,
'router-link-exact-active': isExactActive.value,
},
},
slots.default?.({ route: route.value, isActive: isActive.value })
)
},
})
8. 路由懒加载与动态路由
8.1 路由懒加载
路由懒加载利用了 JavaScript 的 动态 import 语法,让组件在需要时才加载,而非打包到主 bundle 中:
// ❌ 不使用懒加载 —— 所有组件都打包在一起
import Home from './views/Home.vue'
import About from './views/About.vue'
import User from './views/User.vue'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User },
]
// ✅ 使用懒加载 —— 每个路由生成独立 chunk
const routes = [
{ path: '/', component: () => import('./views/Home.vue') },
{ path: '/about', component: () => import('./views/About.vue') },
{ path: '/user/:id', component: () => import('./views/User.vue') },
]
懒加载的实现原理:
// Webpack/Vite 遇到动态 import 时:
// 1. 将 import() 的模块单独打包为一个 chunk 文件
// 2. 运行时通过 <script> 标签异步加载
// Vue Router 内部处理异步组件:
function resolveAsyncComponents(matched) {
return matched.map((record) => {
return () => {
const component = record.components.default
if (typeof component === 'function') {
// 执行 import(),返回 Promise
return component().then((resolved) => {
// 用解析后的组件替换原来的函数
record.components.default = resolved.default || resolved
})
}
}
})
}
8.2 动态路由(addRoute)
Vue Router 支持在运行时动态添加/删除路由,常用于基于权限的菜单系统:
const router = createRouter({
routes: [
{ path: '/', component: Home },
{ path: '/login', component: Login },
],
})
// 用户登录后,根据权限动态添加路由
async function onLogin(user) {
const permissions = await fetchPermissions(user.id)
if (permissions.includes('admin')) {
router.addRoute({
path: '/admin',
component: () => import('./views/Admin.vue'),
children: [
{ path: 'users', component: () => import('./views/AdminUsers.vue') },
{ path: 'settings', component: () => import('./views/AdminSettings.vue') },
],
})
}
// 添加路由后需要手动触发导航才能生效
router.replace(router.currentRoute.value.fullPath)
}
// 删除路由
const removeRoute = router.addRoute({ path: '/temp', component: Temp })
removeRoute() // 调用返回的函数即可删除
// 也可以按名称删除
router.addRoute({ name: 'temp', path: '/temp', component: Temp })
router.removeRoute('temp')
9. 滚动行为控制
Vue Router 允许你自定义路由切换时的滚动行为:
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// 如果有保存的位置(浏览器前进/后退),恢复位置
if (savedPosition) {
return savedPosition
}
// 如果目标路由有 hash(锚点),滚动到锚点
if (to.hash) {
return {
el: to.hash, // 如 #section-1
behavior: 'smooth', // 平滑滚动
}
}
// 默认滚动到顶部
return { top: 0 }
},
})
10. Vue Router 3 与 Vue Router 4 对比
| 特性 | Vue Router 3(Vue 2) | Vue Router 4(Vue 3) |
|---|---|---|
| 创建方式 | new VueRouter({ routes }) | createRouter({ history, routes }) |
| 模式配置 | mode: 'history' / mode: 'hash' | createWebHistory() / createWebHashHistory() |
| 响应式实现 | Vue.util.defineReactive | shallowRef |
| 组件注入 | Vue.mixin + this.$router | provide/inject + useRouter() |
| 通配符路由 | path: '*' | path: '/:pathMatch(.*)*' |
| 导航守卫 next | next() 必须调用 | 可选,支持返回值 |
| Composition API | 不支持 | useRouter(), useRoute() |
<router-view> | 直接渲染 | 支持作用域插槽 v-slot |
| TypeScript | 有限支持 | 完整类型推导 |
Vue Router 4 守卫的简化写法:
// Vue Router 3 —— 必须调用 next()
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
} else {
next()
}
})
// Vue Router 4 —— 直接返回即可,更简洁
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return '/login'
}
// 不返回或返回 true 表示放行
})
11. 手写一个迷你 Vue Router
为了加深理解,让我们实现一个最简化版的 Vue Router:
// mini-router.js
import { ref, h, provide, inject, defineComponent, computed } from 'vue'
// 创建路由器
function createRouter(options) {
const { routes, history } = options
// 响应式的当前路径
const currentPath = ref(window.location.pathname)
// 路由匹配
function matchRoute(path) {
return routes.find((route) => route.path === path)
}
// 当前路由
const currentRoute = computed(() => matchRoute(currentPath.value))
// 导航方法
function push(path) {
window.history.pushState({}, '', path)
currentPath.value = path
}
function replace(path) {
window.history.replaceState({}, '', path)
currentPath.value = path
}
// 监听浏览器前进/后退
window.addEventListener('popstate', () => {
currentPath.value = window.location.pathname
})
const router = {
currentRoute,
push,
replace,
install(app) {
app.provide('router', router)
app.provide('currentRoute', currentRoute)
},
}
return router
}
// RouterView 组件
const RouterView = defineComponent({
name: 'RouterView',
setup() {
const currentRoute = inject('currentRoute')
return () => {
const route = currentRoute.value
if (route && route.component) {
return h(route.component)
}
return h('div', '404 - 页面未找到')
}
},
})
// RouterLink 组件
const RouterLink = defineComponent({
name: 'RouterLink',
props: {
to: { type: String, required: true },
},
setup(props, { slots }) {
const router = inject('router')
return () =>
h(
'a',
{
href: props.to,
onClick(e) {
e.preventDefault()
router.push(props.to)
},
},
slots.default?.()
)
},
})
export { createRouter, RouterView, RouterLink }
使用方式:
import { createApp } from 'vue'
import { createRouter, RouterView, RouterLink } from './mini-router'
const Home = { template: '<h1>首页</h1>' }
const About = { template: '<h1>关于</h1>' }
const router = createRouter({
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About },
],
})
const app = createApp({
components: { RouterView, RouterLink },
template: `
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">关于</RouterLink>
</nav>
<RouterView />
`,
})
app.use(router)
app.mount('#app')
12. 面试常见问题
Q1: Vue Router 的 Hash 模式和 History 模式有什么区别?
参考回答:
Hash 模式使用 URL 的 # 部分来模拟路径,通过 hashchange 事件监听变化,不需要服务器配置,兼容性好,但 URL 带有 # 不美观且不利于 SEO。
History 模式使用 HTML5 的 History API(pushState / replaceState)管理 URL,地址干净没有 #,对 SEO 更友好,但需要服务端配置回退路由(所有路径都返回 index.html),否则刷新会 404。
两者本质区别在于:Hash 模式的 # 后内容不会发送到服务器,而 History 模式的路径会发送到服务器。
Q2: Vue Router 的导航守卫有哪些?执行顺序是什么?
参考回答:
Vue Router 的导航守卫分为三类:
- 全局守卫:
beforeEach、beforeResolve、afterEach - 路由独享守卫:
beforeEnter - 组件内守卫:
beforeRouteLeave、beforeRouteUpdate、beforeRouteEnter
完整执行顺序:
- 触发离开组件的
beforeRouteLeave - 调用全局
beforeEach - 在复用组件中调用
beforeRouteUpdate - 调用路由配置的
beforeEnter - 解析异步路由组件
- 在被激活的组件中调用
beforeRouteEnter - 调用全局
beforeResolve - 导航被确认
- 调用全局
afterEach - 触发 DOM 更新
- 调用
beforeRouteEnter中next的回调函数
Q3: 路由懒加载是什么?原理是什么?
参考回答:
路由懒加载是指在路由配置中使用动态 import() 语法引入组件,使得组件代码不会被打包到主 bundle 中,而是生成独立的 chunk 文件,在用户访问对应路由时才按需加载。
{ path: '/about', component: () => import('./views/About.vue') }
原理:
- 打包工具(Webpack/Vite)遇到
import()语法时,会将目标模块单独打包为一个 JS 文件(chunk) - 运行时,Vue Router 在导航到该路由时执行
import()函数 - 浏览器通过动态创建
<script>标签来加载这个 chunk - 加载完成后,Vue Router 将解析出的组件注册并渲染
好处是减小首屏加载体积,提升首屏加载速度。
Q4: $route 和 $router 的区别是什么?
参考回答:
$router是路由器实例,是全局的路由管理对象,包含路由的跳转方法(push、replace、go、back、forward)和配置方法(addRoute、removeRoute)等$route是当前激活的路由信息对象,包含当前路由的信息(path、params、query、hash、fullPath、matched、meta等),是只读的
简单记忆:$router 用来操作路由,$route 用来获取路由信息。
在 Vue 3 + Composition API 中,对应使用 useRouter() 和 useRoute()。
Q5: Vue Router 怎么实现动态路由?有什么应用场景?
参考回答:
Vue Router 通过 router.addRoute() 方法实现动态路由,可以在运行时添加新的路由规则。
router.addRoute({ path: '/admin', component: Admin })
router.addRoute('parent', { path: 'child', component: Child }) // 添加子路由
主要应用场景:
- 权限管理:根据用户登录后获取的权限动态生成可访问的路由,实现菜单和页面级别的权限控制
- 插件系统:允许第三方插件注册自己的路由
- 多租户系统:不同租户显示不同的功能页面
注意事项:添加路由后需调用 router.replace() 或 router.push() 才能使新路由生效。
Q6: Vue Router 中 <router-view> 是怎么实现的?
参考回答:
<router-view> 是一个函数式组件,核心原理是:
- 通过
inject注入当前的响应式路由对象currentRoute - 维护一个
depth(深度)值,用于处理嵌套路由 - 从
currentRoute.matched[depth]中获取对应层级的路由记录 - 使用
h()渲染该路由记录对应的组件
嵌套路由的实现关键在于 depth 机制:每层 <router-view> 通过 provide/inject 传递并递增深度值,从而从 matched 数组中取到正确层级的组件。
Q7: 如何实现路由切换的过渡动画?
参考回答:
Vue Router 4 中,<router-view> 支持 v-slot 作用域插槽,可以结合 <Transition> 组件实现过渡动画:
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
关键点:
- 使用
v-slot解构出Component和route mode="out-in"确保旧组件离开后新组件再进入:key="route.path"确保路由变化时触发过渡
Q8: keep-alive 和 router-view 如何配合使用?
参考回答:
<keep-alive> 可以缓存不活跃的路由组件,避免重复渲染和丢失状态:
<!-- Vue Router 4 写法 -->
<router-view v-slot="{ Component }">
<keep-alive :include="['Home', 'List']">
<component :is="Component" />
</keep-alive>
</router-view>
配合路由 meta 做精细化缓存控制:
const routes = [
{ path: '/', component: Home, meta: { keepAlive: true } },
{ path: '/detail/:id', component: Detail, meta: { keepAlive: false } },
]
<router-view v-slot="{ Component, route }">
<keep-alive>
<component :is="Component" v-if="route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!route.meta.keepAlive" />
</router-view>
被缓存的组件会触发 activated 和 deactivated 生命周期钩子,可以在这些钩子中做数据刷新等操作。
Q9: 如何解决 Vue Router 中刷新页面后参数丢失的问题?
参考回答:
这取决于参数的传递方式:
- 路径参数(params)在路由定义中声明:如
/user/:id,刷新后不会丢失,因为参数是 URL 的一部分 - query 参数:如
/search?keyword=vue,刷新后不会丢失 - params 参数但路由中未定义占位符:Vue Router 4 中已不允许这种用法(Vue Router 3 中通过
name+params传递的未定义在路径中的参数,刷新后会丢失)
解决方案:
- 优先使用路径参数或 query 参数
- 需要持久化的数据可存入
sessionStorage或localStorage - 使用 Vuex/Pinia 等状态管理(但刷新后同样需要持久化)
13. 总结
理解 Vue Router 的核心原理,需要抓住以下几个关键点:
- 前端路由的本质:监听 URL 变化,更新视图但不刷新页面
- Hash 与 History:两种不同的 URL 管理策略,各有优劣
- 响应式桥梁:通过
shallowRef(Vue 3)让路由变化驱动视图更新 - 路由匹配:路径字符串 → 正则表达式 → URL 匹配 → 参数提取
- 导航守卫:本质是一个异步队列,按固定顺序依次执行
- RouterView:通过
depth和matched实现嵌套路由渲染 - 路由懒加载:利用动态
import()实现代码分割和按需加载