一次界面库迁移之后,本地从「网络接入」点到「插件管理」,浏览器地址已经变成插件页,右侧内容区却是空白。刷新同一地址,页面又正常出现。控制台没有报错。
后面有一版修复把菜单完全点不动了。两次现象叠在一起,才把原因钉死。
现象
迁移完成后,侧栏菜单的点击行为是:
-
地址栏从网络接入变成插件页
-
顶栏和侧栏还在
-
内容区空白
-
插件页挂载时会打的日志没有出现
-
控制台没有红色报错
-
在空白状态下看内容容器,子节点是空数组,页面根节点和画布节点都不存在
-
手动刷新当前地址,插件页正常渲染
这说明路由地址已经换了,插件页组件没有被创建。刷新能打开,说明组件文件、接口和样式本身都能工作。
先排除掉的方向
排查时依次看过这些点,它们都能造成「像是没渲染」,但和这次的证据对不上。
样式把页面藏住了。 若只是高度为 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」。