08|Vue Router 3 → 4 迁移
Router 3 的初始化、参数复用和 next 守卫先在原版本维护篇连续学习,再回到本篇比较迁移后的调用与失败契约。
第六章概览 / 08。目标:迁移不是替换构造函数;先列旧应用的导航合同,再逐项核对异步、history、匹配、视图和插件。
创建方式与 history
// Router 3 / Vue 2
const router = new Router({ mode: 'history', base: '/app/', routes })
// Router 4 / Vue 3
const router = createRouter({
history: createWebHistory('/app/'),
routes,
})2
3
4
5
6
7
8
Router 4 不再是 class;mode/base 被 history 实例取代。routes 是必填项,即使先传空数组再动态添加。hash/memory 模式也由对应创建函数表示。
高影响差异表
| Router 3 | Router 4 | 回归重点 |
|---|---|---|
new Router() | createRouter() | app.use 安装顺序 |
mode/base | history: createWebHistory(base) | 子路径、深链 fallback |
router.onReady(cb) | await router.isReady() | 初始导航总是异步 |
| push/replace 完成/失败 callbacks | Promise | failure resolve 与异常 reject |
catch-all * | /:pathMatch(.*)* | 404 与命名生成 |
router.currentRoute 普通对象 | router.currentRoute.value(Ref) | 组件外读取与 watch |
scroll { x, y } | { left, top } | 返回/锚点恢复 |
外层 <transition><router-view> | RouterView v-slot 内包动态组件 | 生命周期与 KeepAlive |
router.addRoutes() | router.addRoute() / 返回移除函数 | 动态权限路由清理 |
router.match() | router.resolve() | 测试与链接生成 |
Router 4 的 params 编码、hash 解码和路径匹配器也有变化。不要把旧快照字符串当真值;按真实 URL、params/query 和最终组件断言。
分辨旧工程的四层配置
拿到new Router({mode:'history'})和webpackChunkName注释时,应分开看:Router构造方式、浏览器history、构建器分包、页面加载失败。component:()=>import(...)的延迟加载思想仍可保留,但webpack注释不能证明Vite会生成相同chunk名称。先用实际构建的资源URL核对分包,再模拟旧HTML引用已删除chunk的失败。
旧class组件常用装饰器监听整个$route。迁移后不要机械改成watch(route, ...):先列出请求真正依赖的参数。只改hash要滚动,不应再次加载用户;改id才是资源变化。URL变化看似只有一条消息,里面却有不同责任,这也是本章先教params/query再讨论迁移的原因。
守卫从 callback 收敛为 return
// 旧式
router.beforeEach((to, from, next) => {
if (needsLogin(to)) next('/login')
else next()
})
// Router 4 推荐
router.beforeEach(to => {
if (needsLogin(to)) return { name: 'login' }
})2
3
4
5
6
7
8
9
10
next 仍兼容,但迁移时适合改成单返回路径,减少漏调/双调。不要在同一个 guard 一半 next、一半 return。
不要覆盖 Router 的 history state
Router 4 使用 history.state 保存滚动位置、前后位置等内部信息。旧代码若执行 history.replaceState({}, '', url) 会擦掉这些字段。优先改用 router.replace();确需追加自定义 state 时保留现有对象:
history.replaceState({ ...history.state, panel: 'help' }, '')自定义值必须可结构化克隆、体积小,且不能替代可分享的 params/query。
动态路由的生命周期
const removeRoute = router.addRoute({
path: '/admin',
name: 'admin',
component: () => import('./AdminView.vue'),
})
// 权限撤销或插件卸载
removeRoute()2
3
4
5
6
7
8
添加 route 不会自动把当前 URL 重新导航到新匹配,需要 router.replace(router.currentRoute.value.fullPath)。在 guard 内动态添加时,应返回目标位置触发新匹配,并设置“已添加”条件防止无限循环。
迁移顺序
- 固定 Router 3 的 URL/组件、重定向、404、守卫和滚动行为测试。
- 迁 createRouter/history/base,让深链先工作。
- 迁 catch-all、动态路由和命名导航。
- 迁 Promise 与 guard 返回值,区分 navigation failure/exception。
- 迁 RouterView slot、KeepAlive、Transition、scrollBehavior。
- 回归分析、权限、进度条等依赖 router hooks 的插件。
当前 Router 5 是面向后续版本的过渡发布;对未使用 unplugin-vue-router 文件路由的 Router 4 应用,官方迁移说明没有业务 API breaking change。本页仍聚焦更常见、影响更大的 3→4 迁移。
练习:迁移审计
把一个含 mode:'history'、base:'/admin/'、onReady、*、next 守卫、{x,y} 滚动和外层 KeepAlive 的 Router 3 配置改为 Router 4。要求每项写“旧行为断言—新 API—失败测试”,并保留服务器 /admin/** → /admin/index.html fallback 责任。
参考答案:第 08 题。
本页结论
迁移完成的证据是历史栈、深链、异步结果、视图缓存和滚动都保持合同;能编译只是第一步。