跳到主要内容

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 APIpushState / replaceState)来管理 URL。

https://example.com/home
https://example.com/about

核心原理:

  • history.pushState() 可以修改 URL 但 不会触发页面刷新
  • 通过监听 popstate 事件感知浏览器前进/后退
  • 注意: pushStatereplaceState 本身 不会触发 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 的核心由以下几部分组成:

  1. 路由匹配器(Matcher):将 URL 路径与路由配置进行匹配
  2. 历史管理(History):封装 Hash / History API,统一接口
  3. 响应式路由状态(currentRoute):与 Vue 响应式系统绑定
  4. 导航守卫系统:提供路由跳转的生命周期钩子
  5. 内置组件<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.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>

<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.defineReactiveshallowRef
组件注入Vue.mixin + this.$routerprovide/inject + useRouter()
通配符路由path: '*'path: '/:pathMatch(.*)*'
导航守卫 nextnext() 必须调用可选,支持返回值
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 APIpushState / replaceState)管理 URL,地址干净没有 #,对 SEO 更友好,但需要服务端配置回退路由(所有路径都返回 index.html),否则刷新会 404。

两者本质区别在于:Hash 模式的 # 后内容不会发送到服务器,而 History 模式的路径会发送到服务器。

Q2: Vue Router 的导航守卫有哪些?执行顺序是什么?

参考回答:

Vue Router 的导航守卫分为三类:

  1. 全局守卫beforeEachbeforeResolveafterEach
  2. 路由独享守卫beforeEnter
  3. 组件内守卫beforeRouteLeavebeforeRouteUpdatebeforeRouteEnter

完整执行顺序:

  1. 触发离开组件的 beforeRouteLeave
  2. 调用全局 beforeEach
  3. 在复用组件中调用 beforeRouteUpdate
  4. 调用路由配置的 beforeEnter
  5. 解析异步路由组件
  6. 在被激活的组件中调用 beforeRouteEnter
  7. 调用全局 beforeResolve
  8. 导航被确认
  9. 调用全局 afterEach
  10. 触发 DOM 更新
  11. 调用 beforeRouteEnternext 的回调函数

Q3: 路由懒加载是什么?原理是什么?

参考回答:

路由懒加载是指在路由配置中使用动态 import() 语法引入组件,使得组件代码不会被打包到主 bundle 中,而是生成独立的 chunk 文件,在用户访问对应路由时才按需加载。

{ path: '/about', component: () => import('./views/About.vue') }

原理:

  1. 打包工具(Webpack/Vite)遇到 import() 语法时,会将目标模块单独打包为一个 JS 文件(chunk)
  2. 运行时,Vue Router 在导航到该路由时执行 import() 函数
  3. 浏览器通过动态创建 <script> 标签来加载这个 chunk
  4. 加载完成后,Vue Router 将解析出的组件注册并渲染

好处是减小首屏加载体积,提升首屏加载速度。

Q4: $route$router 的区别是什么?

参考回答:

  • $router 是路由器实例,是全局的路由管理对象,包含路由的跳转方法(pushreplacegobackforward)和配置方法(addRouteremoveRoute)等
  • $route 是当前激活的路由信息对象,包含当前路由的信息(pathparamsqueryhashfullPathmatchedmeta 等),是只读的

简单记忆:$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 }) // 添加子路由

主要应用场景:

  1. 权限管理:根据用户登录后获取的权限动态生成可访问的路由,实现菜单和页面级别的权限控制
  2. 插件系统:允许第三方插件注册自己的路由
  3. 多租户系统:不同租户显示不同的功能页面

注意事项:添加路由后需调用 router.replace()router.push() 才能使新路由生效。

Q6: Vue Router 中 <router-view> 是怎么实现的?

参考回答:

<router-view> 是一个函数式组件,核心原理是:

  1. 通过 inject 注入当前的响应式路由对象 currentRoute
  2. 维护一个 depth(深度)值,用于处理嵌套路由
  3. currentRoute.matched[depth] 中获取对应层级的路由记录
  4. 使用 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 解构出 Componentroute
  • mode="out-in" 确保旧组件离开后新组件再进入
  • :key="route.path" 确保路由变化时触发过渡

Q8: keep-aliverouter-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>

被缓存的组件会触发 activateddeactivated 生命周期钩子,可以在这些钩子中做数据刷新等操作。

Q9: 如何解决 Vue Router 中刷新页面后参数丢失的问题?

参考回答:

这取决于参数的传递方式:

  1. 路径参数(params)在路由定义中声明:如 /user/:id,刷新后不会丢失,因为参数是 URL 的一部分
  2. query 参数:如 /search?keyword=vue,刷新后不会丢失
  3. params 参数但路由中未定义占位符:Vue Router 4 中已不允许这种用法(Vue Router 3 中通过 name + params 传递的未定义在路径中的参数,刷新后会丢失)

解决方案:

  • 优先使用路径参数或 query 参数
  • 需要持久化的数据可存入 sessionStoragelocalStorage
  • 使用 Vuex/Pinia 等状态管理(但刷新后同样需要持久化)

13. 总结

理解 Vue Router 的核心原理,需要抓住以下几个关键点:

  1. 前端路由的本质:监听 URL 变化,更新视图但不刷新页面
  2. Hash 与 History:两种不同的 URL 管理策略,各有优劣
  3. 响应式桥梁:通过 shallowRef(Vue 3)让路由变化驱动视图更新
  4. 路由匹配:路径字符串 → 正则表达式 → URL 匹配 → 参数提取
  5. 导航守卫:本质是一个异步队列,按固定顺序依次执行
  6. RouterView:通过 depthmatched 实现嵌套路由渲染
  7. 路由懒加载:利用动态 import() 实现代码分割和按需加载