地址变了,页面是空的:一次 Vue Router 菜单切换复盘

一次界面库迁移之后,本地从「网络接入」点到「插件管理」,浏览器地址已经变成插件页,右侧内容区却是空白。刷新同一地址,页面又正常出现。控制台没有报错。

后面有一版修复把菜单完全点不动了。两次现象叠在一起,才把原因钉死。

现象

迁移完成后,侧栏菜单的点击行为是:

  • 地址栏从网络接入变成插件页

  • 顶栏和侧栏还在

  • 内容区空白

  • 插件页挂载时会打的日志没有出现

  • 控制台没有红色报错

  • 在空白状态下看内容容器,子节点是空数组,页面根节点和画布节点都不存在

  • 手动刷新当前地址,插件页正常渲染

这说明路由地址已经换了,插件页组件没有被创建。刷新能打开,说明组件文件、接口和样式本身都能工作。

先排除掉的方向

排查时依次看过这些点,它们都能造成「像是没渲染」,但和这次的证据对不上。

样式把页面藏住了。 若只是高度为 0 或透明,组件仍会挂载,日志仍会打印,DOM 里仍能找到页面根节点。这次三者都没有。

离开画布页时卸载抛错,把下一次渲染打断了。 网络接入页用了画布组件,离开时确实容易在卸载阶段出错。若是这样,控制台会有异常,刷新因为不经过这次卸载所以正常。这次没有异常,而且内容区是完全空的,不是画布残留。

router-view 外层的 <transition> 把新页面吞掉了。 布局里原来包了一层没有对应样式的过渡。过渡在离开旧页面时,有可能旧节点拆掉、新节点不再插入,而且不报错。去掉过渡之后,空白仍在。过渡可以让切换更脆,但不是这次的根因。

给页面补 :key。 缓存分支上的 key 原先没有作用到实际页面上。补到组件上之后,从网络接入点到插件管理仍然空白。key 解决的是同一类组件被复用,解决不了「匹配结果里根本没有这个页面」。

真正的原因

菜单点击原来是按路由名称跳转:

js 复制代码
router.push({

name: menu.name,

params: route.params

})

插件页在路由表里不是这个名称自己对应的那条记录,而是它下面的一个子路由。这个子路由的 path 是空字符串,并且没有自己的名称。父子拼出来的地址都是 /plugin 这一类路径,所以地址栏看不出差别。

Vue Router 4 里,这两种跳法的匹配结果不同。

按名称跳转时,匹配器只取出这个名称对应的记录,再向上带上父级。空路径的子记录不会进入 matched。布局还在,因为它是父级;布局里面的 router-view 找不到下一级组件,于是渲染成注释节点。内容区没有元素,组件的 onMounted 不会执行,也不会有报错。

刷新时浏览器按地址匹配。空路径子路由和父级的路径分数相同,注册时子路由会排在同分数的祖先前面。路径匹配命中的是子路由,matched 里同时有父级和子页面,插件页就被挂上了。

官方在添加这类路由时会给出警告:父级有名称,子级是空路径且没有名称时,用父级名称导航不会渲染这个子页面。名称应该放在真正要显示的那一层;如果必须留在父级,跳转就要走路径,不要走名称。

所以「地址对、刷新就好、点击是空白、没有报错」是同一件事:点击和刷新用了两种匹配。

一次改错,菜单直接点不动

确认原因之后,曾把点击改成 router.push(menu.fullPath),并且在 fullPath 为空时直接返回。结果是侧栏菜单完全切不了。

侧栏读的是一份菜单状态。这份状态在「给路由补全路径」之前就被深拷贝存进去了。fullPath 写在后来注册进路由表的那一份对象上,侧栏这份拷贝里没有这个字段。

条件判断看到的永远是空值,push 没有执行。上一版至少还能改地址,这一版连地址都不改了。

菜单数据上的 fullPath 不能当成已经存在的字段。路径要从路由表解析。

修复

点击时仍用菜单上的名称找到路由,但真正跳转用解析出来的路径:

js 复制代码
const target = router.resolve({

name: menu.name,

params: route.params

})

router.push(target.path)

resolve 按名称得到父级路径,例如 /plugin。push 这个路径时走的是地址匹配,空路径子页面会一起进入 matched,和刷新一致。

名称本身仍然要保留。它是菜单和路由表之间现成的关联。缺的是最后一步必须按路径进入匹配器。

布局上的 router-view 改成只渲染当前组件,不再包无样式的 <transition>。过渡不是根因,但切换时它会让「旧页面已离开、新页面未挂上」更难分辨。

以后可以怎么少走弯路

出现「地址对、内容空、刷新才有、控制台无报错」时,先看当前路由的 matched,不要先改样式和过渡。重点看最后一级有没有组件,以及这一级是按名称进来的还是按路径进来的。

动态菜单同时满足下面几条时,按名称跳转就会空白:

  • 名称写在父级

  • 真正的页面是 path: '' 的子路由

  • 这个子路由没有自己的名称

菜单如果来自接口,再确认侧栏用的对象是不是路由表里的那一份。深拷贝发生在补全路径之前,侧栏上就不会有 fullPath。用一份尚未补全的字段做开关,会把整个菜单点死。

界面库迁移会让人先怀疑新组件、画布卸载和全局样式。这次内容区是空节点,挂载日志也没有,说明组件实例没有创建。这类证据应该先把排查从「怎么画出来」拉回到「路由有没有把页面放进 matched」。

相关推荐
JavaGuide1 小时前
推荐一个好用 DeepSeek Harness 插件,直接把 Agent 上下文给可视化了!
前端·后端
数据掘金1 小时前
白屏故障定位:从资源请求到路由渲染的排查顺序
前端
程序员Sunday1 小时前
ES Module 和 CommonJS 有什么区别?循环依赖时会发生什么?
前端·javascript·面试
玲珑意1 小时前
Vue 3 + DAG拓扑排序 + SSE流式人机协同:现代AI工作流编排系统的工程化实践
前端
三十而立洋1 小时前
TypeScript 7.0 深度解析:新特性、Go 原生编译器及与 TypeScript 6.0 的区别
前端·typescript·前端工程化
编程之道1 小时前
从VUE2到VUE3学习过程遇到的一些困惑
前端
用户817595220131 小时前
QNAP NAS 搭建个人网站实战
前端
梦里寻码2 小时前
给 AI Agent 搭围栏:Monorepo 是被低估的 Agent 基础设施
前端·后端·github
异彩2 小时前
一个纯前端的 Chrome 插件:不搭代理,也能把接口返回编圆
前端·chrome·mockito