那个从没改过的页面,为什么突然出现异常?

问题背景
这个项目已经很久没有更新了。在走航任务面板中,有一段任务名称通过 v-show="isRunning" 控制显示,旁边还设置了 style="display: flex",这个写法运行了好几年,一直都很稳定。
然而今天却突然出现了故障。
测试人员发来了一张截图:任务已经结束,isRunning 的值是 false,但任务名称却依然显示在面板上。
询问:这个页面最近修改过吗?
回答:没有。
再问:那为什么突然之间就出问题了呢?
这个问题听起来确实让人头疼,但无论如何都必须排查。
排查过程
首先,从自身代码开始检查。
打开 StartTask.vue 文件:
复制代码
检查数据状态:isRunning 在任务结束后确实是 false。
检查组件更新:组件已经重新渲染了。
检查样式影响:其他位置没有额外添加 display 样式。
清除了缓存和 node_modules,重新启动服务,问题依然存在。
再进一步核实业务逻辑:结束任务的状态流转似乎也没有问题。
这就有点奇怪了。v-show 在条件为假时,会为元素添加 display: none,这样一个基础功能怎么会突然失效?
缩小问题范围
为了快速定位,我们制作了一个最小复现案例:
复制代码内容
点击按钮后,元素应该隐藏,但它却依然显示。
去掉 style,只保留 v-show,功能正常。
去掉 v-show,只保留 style,样式也正常。
但两者同时存在时,问题就出现了。
结论非常明确:问题不是出在业务逻辑上,而是同一个标签上的 v-show 和 style 发生了冲突。
但这仍然无法解释为什么之前一直运行良好,偏偏今天才暴露出来。同样的代码写法在项目中已经存在了很长时间。
真正的根本原因
代码没有改动,那就需要检查运行环境是否发生了变化。
查看 package.json 中的配置:
复制代码"vue": "^2.7.14"
请注意这个 ^ 符号。它表示允许安装兼容范围内的更新版本。对于 2.7.x 系列来说,^2.7.14 可以解析到 2.7.15、2.7.16 等版本。
再查看实际安装的版本:
复制代码vue@^2.7.14:
version "2.7.16"
这样一来,问题就清晰了。
项目长久没有更新。旧电脑上的 node_modules 还是当时安装的版本,很可能是 2.7.14 或 2.7.15。在本地每天运行,页面一直正常。
今天换了新电脑,拉取代码并重新安装依赖。package.json 中仍然是 ^2.7.14,包管理器按照规则安装了范围内最新的正式版:2.7.16。
代码一行没改,但 Vue 的小版本号跳了一格,行为就完全不同了。
2.7.16 版本的具体变更
Vue 2.7.16 代号为 Swan Song,是 Vue 2 的最终正式版。它没有引入新功能,主要是进行收尾的 bug 修复,以及向 Vue 3 对齐一些类型定义。
其中有一个关键改动:
为了修复 style 合并问题(例如 border 和 border-bottom 互相覆盖),2.7.16 版本去掉了新旧值相同就跳过执行的判断逻辑,每次更新都会将 style 对象中的属性重新写入一遍。
实际产生的影响是:
v-show=false首先将元素设置为display: none- 在同一次更新中,style 模块会再次执行
- 内联的
style中包含了display: flex setProp(el, 'display', 'flex')将none覆盖掉了
最终导致 v-show 失效,元素又重新显示出来。
官方 issue 记录:vuejs/vue#13140。有人曾提交过修复 PR,但 Vue 2 已经进入 EOL(生命周期结束)阶段,不会再发布补丁了。
因此,这并非指令写法错误,而是上游为了修复 A 问题引入的回归,恰好影响到了 B 场景。我们今天才发现,是因为新环境第一次真正运行到了 2.7.16 版本。
关于 lock 文件的重要性
回顾整个排查过程,比修改模板更值得记住的经验是:
package.json 中写的是版本范围,而实际安装到哪个版本,取决于 lock 文件。
如果 lock 文件被认真提交并正确使用:
- 旧电脑和新电脑会安装到同一个
vue版本 - 本地正常、他人环境异常这种互相扯皮的情况会大大减少
- 版本升级会变成一次明确的变更,而不是重新安装依赖时顺手带来的意外
这次问题的真正隐藏原因就是:并非页面突然坏了,而是换电脑后第一次安装到了 2.7.16 版本。
所以,lock 文件该提交到版本库就提交,不要把它当作可有可无的东西。
解决方案
1. 降级到 2.7.15 版本
这是最直接的方法。在 2.7.15 版本中,v-show 不会被 style 覆盖。
代价是会丢失 2.7.16 版本中的其他修复(如 keep-alive 泄漏、一些响应式边界问题、类型对齐等)。适合用来临时止血,但不太建议长期停留在该版本。
2. 将 v-show 替换为 v-if
复制代码
改动量很小,能够立即生效。元素会进行销毁和重建,对于普通 UI 通常没有问题;但对于地图、图表、内部状态较多的组件,需要谨慎评估。
3. 将静态 style 改为 class 样式
复制代码
复制代码.task-name-row {
display: flex;
justify-content: center;
}
v-show 写入的是内联 display: none,其优先级高于样式表中的 display: flex,因此不会被覆盖。布局也更清晰整洁。
在实际项目中,任务面板采用了方案 2,方案管理弹窗则将 display: flex 提取到 class 中,使用了方案 3。根据具体场景灵活选择即可。
总结
排查工作到此结束,代码改动量其实并不大。
以后如果再遇到“从没改过却突然坏了”的情况,可以先问一句:你安装到的,还是同一个版本吗?
相关链接
- Issue:在 2.7.16 版本中同时使用 v-show 和 :style 无法获得预期效果
- 根因提交:fix(style): always set new styles
- 未合并的修复尝试:PR #13198
- Vue 2 生命周期结束说明:Vue 2 End of Life
