Vue Router 的 hash 和 history 模式区别?导航守卫怎么用?

2026年 阅读约 9 分钟 面试指南 · 前端面试

Vue Router与状态管理面试题:hash与history模式区别及Nginx配置、完整导航解析流程、导航守卫做登录鉴权、动态路由权限、Vuex与Pinia对比,分三层讲解。

一句话总结

hash 模式路由在 # 后(# 不发给服务器,无需服务端配置,兼容好但 URL 丑、SEO 弱);history 模式用真实路径(好看,靠 pushState/replaceState + popstate,刷新必须服务端兜底到 index.html)。导航守卫是路由跳转的拦截链,beforeEach 里做 token 鉴权;状态管理选 Pinia(Vue3 官方推荐:无 mutation、完整 TS 推导、组合式写法)。

初级理解

对比项hashhistory
URL 形态/#/user?id=1/user?id=1
底层 APIhashchange 事件pushState / replaceState + popstate
服务端配置不需要需要(404 兜底)
刷新永远请求 /,安全请求真实路径,未配置则 404
锚点功能冲突(# 被占用)正常

编程式导航:router.push()(保留历史)、replace()(替换当前记录,登录后常用)、go(-1)。传参两种:query(?id=1,刷新不丢)和 params(路径段 /user/:id,Vue Router 4 中 push 的 params 不带路径名会被丢弃——刷新即丢,别再用它传关键数据)。

Vuex vs Pinia 一句话:Vuex 是 state/getter/mutation/action 五件套,改数据必须经 mutation(同步);Pinia 砍掉 mutation,action 里直接改,天然支持 TS 和组合式 store。

中级深入

完整的导航解析流程(守卫执行顺序):

1. 触发导航(组件/编程式) 2. 组件失活前的 beforeRouteLeave(离开组件) 3. 全局 router.beforeEach 4. 复用组件的 beforeRouteUpdate 5. 路由配置里的 beforeEnter 6. 目标组件内 beforeRouteEnter(此时拿不到 this/组件实例) 7. 全局 router.beforeResolve 8. 导航被确认 → afterEach 9. DOM 更新 → beforeRouteEnter 的 next(vm => …) 回调(唯一能拿到实例的时机) 10. 异步组件/路由懒加载完成

守卫三件套记忆:全局(beforeEach/beforeResolve/afterEach)、路由级(beforeEnter)、组件级(beforeRouteEnter/Update/Leave)。beforeEach 的三个参数 (to, from, next)——Vue Router 4 也支持返回值风格:return false 取消、return 路径重定向、不返回则放行。

路由懒加载:把组件编译成独立 chunk,进入时才加载:component: () => import('./views/User.vue')。这是首屏优化的标配(配合 webpackChunkName/vite 的 rollupOptions 注释分包)。

高级拓展

动态路由与按钮级权限:登录后按接口返回的菜单表 router.addRoute() 动态注册路由(前端只留静态基础路由);按钮级权限用自定义指令或组件包裹判断权限码。

// 动态注册 + 404 兜底必须最后加 asyncRoutes.forEach(r => router.addRoute(r)) router.addRoute({ path: '/:pathMatch(.*)*', component: NotFound }) // Vue Router 4 的 404 写法(取代了 Vue2 的 path: '*')

history 模式的 Nginx 配置(生产必背):

location / { try_files $uri $uri/ /index.html; # 先找真实文件,找不到回退 index.html }

Pinia 组合式 store 与持久化:defineStore 里可以像 setup 一样写 ref/computed/函数;持久化用 pinia-plugin-persistedstate(localStorage),或手动 watch 同步——token 这类敏感信息建议只放 sessionStorage 或内存,避免 XSS 读取。

export const useUserStore = defineStore('user', () => { const token = ref('') const isLogin = computed(() => !!token.value) async function login(form) { token.value = (await api.login(form)).token } return { token, isLogin, login } }, { persist: true })
面试加分项:提 scrollBehavior(返回保存的滚动位置实现"返回列表还在原位置")和路由过渡(transition + meta.depth 控制动画方向),说明你有真实项目细节。

实战场景

场景一:登录鉴权 + 白名单

router.beforeEach(async (to) => { const user = useUserStore() if (whiteList.includes(to.path)) return true if (!user.token) return { path: '/login', query: { redirect: to.fullPath } } if (!user.menus.length) { await user.loadMenus() // 拉菜单并 addRoute return to.fullPath // 重新进入一次,匹配新路由 } return true })

场景二:刷新后动态路由丢失

// 现象:刷新白屏 404——内存里的 addRoute 全没了 // 解法:beforeEach 里检测"菜单未加载"就重新拉取(上面的写法已覆盖) // 或把菜单表持久化,路由守卫恢复注册后再 return to.fullPath 重进

场景三:多标签页(Tab)系统缓存路由组件

// meta 里标记缓存名,router-view 外包 keep-alive <router-view v-slot="{ Component }"> <keep-alive :include="tabStore.cachedNames"> <component :is="Component" :key="$route.fullPath" /> </keep-alive> </router-view> // 关闭 Tab 时从 cachedNames 移除 → 下次进入重新挂载

面试模拟

Q:history 模式刷新 404 的原因和解决方案?

A:history 的路径是真实 HTTP 请求,服务器上并没有 /user/a/1 这个文件,所以 404。解决:Nginx try_files $uri $uri/ /index.html 兜底到单页入口;Node 服务则配历史回退中间件。注意回退会掩盖真实的资源 404,要给静态资源目录单独的 location。

Q:为什么 Vue3 生态从 Vuex 迁到 Pinia?

A:Pinia 的改进点:① 去掉 mutation——同步异步都在 action,模板字符串式的 commit 消失;② 完整 TS 类型推导(Vuex 的类型推导一直很弱);③ 天然支持组合式写法和代码分割(每个 store 独立文件按需引入);④ 模块化天然扁平,不用 modules 嵌套命名空间;⑤ Vue 作者直接维护、devtools 支持。Vuex 官方也建议新项目用 Pinia。

Q:beforeRouteEnter 里为什么拿不到 this?怎么解决?

A:守卫执行时组件实例还没创建(导航确认在前、挂载在后),this 是 undefined。官方给的口子是 next 回调:beforeRouteEnter(to, from, next) { next(vm => console.log(vm)) },在 DOM 更新后执行且唯一支持传实例。Vue Router 4 里也可改用 onBeforeRouteUpdate/onBeforeRouteLeave 组合式 API(它们在 setup 内执行,天然能访问响应式上下文;注意 onBeforeRouteEnter 不存在,组件内进入钩子仍用 beforeRouteEnter)。