为什么我的Vue组件更新后DOM不刷新?响应式追踪排查记
周五下午,测试在群里丢来一句:“点‘标记完成’后,数据明明变了,为什么按钮还是‘完成’?”我打开页面,控制台打印状态,新值没错;Vue Devtools 里数据也确实更新了,可 DOM 像被冻住一样。于是,一场围绕响应式追踪的排查开始了。
先确认:数据变了,DOM 是不是还没轮到更新?
Vue 的更新是异步的。你在事件回调里修改数据后,DOM 不会立刻同步刷新,而是进入调度队列,等同一轮事件循环结束后批量更新。所以如果你刚改完数据就读取 `innerText`,很可能拿到旧 DOM。先加一句:
await nextTick()
console.log(el.innerText)
如果 `nextTick` 之后 DOM 仍然是旧值,才说明响应式链路真的断了。这个小动作能排除掉一半“假故障”。
检查响应式 API 的使用姿势
Vue 3 里最常见的坑之一是解构 `reactive`:
const state = reactive({ list: [] })
const { list } = state
此时 `list` 只是普通数组,不是响应式代理。模板里绑定 `list` 后,后续 `state.list = res.data` 换了新数组,模板依赖的 `list` 仍然指向旧数组,DOM 自然不会刷新。正确做法是 `const { list } = toRefs(state)`,或者别解构,直接写 `state.list`。
Vue 2 也有类似问题:`data` 中未声明的属性、直接通过索引修改数组、给对象新增属性,都不会触发更新。需要用 `this.$set`,或者替换整个数组/对象。很多人以为“我明明看到数据变了”,但 Vue 的响应式系统并没有“看见”那次修改。
追踪依赖:模板到底依赖了谁?
Vue 2 靠 Dep/Watcher,Vue 3 靠 track/trigger。开发环境下可以用 `onRenderTracked` 和 `onRenderTriggered` 观察组件渲染时收集了哪些依赖:
onRenderTracked((e) => {
console.log('track', e.key, e.target)
})
如果某个数据从未出现在依赖列表里,说明它压根没被响应式系统收集。常见原因包括:数据本身不是响应式的、模板通过方法访问但方法内用了普通变量、`computed` 依赖了非响应式值。还有一个隐蔽点:`v-if` 初始为 false 时,分支内的依赖不会被收集;等条件为 true 后再修改内部数据,才会正常更新。
虚拟 DOM 会不会复用了旧节点?
如果依赖追踪没问题,数据也触发了更新,但视觉上没变,就要怀疑节点复用。`v-for` 的 `key` 用 index,列表顺序变化时 Vue 会复用原节点,输入框内容、动画状态可能错位。`keep-alive` 缓存组件时,激活后要检查 `activated` 钩子里的数据同步。`v-show` 只是切换 `display`,`v-if` 才是销毁重建;如果条件没变,DOM 当然不会变。
异步与竞态:DOM 被旧请求覆盖
我还遇到过一次“假响应式”问题:点击按钮触发两次请求,旧请求返回慢,覆盖了新请求的数据,DOM 最终显示旧值。这不是响应式失效,而是竞态。解决方式可以用请求序号、`AbortController`,或者只允许最后一次更新生效。
一个真实案例复盘
回到开头的问题。项目是 Vue 3 `<script setup>`,代码大概是这样:
const state = reactive({ todos: [] })
const { todos } = state
async function load() {
state.todos = await fetchTodos()
}
模板里写的是:
<li v-for="todo in todos" :key="todo.id">
{{ todo.title }}
</li>
首次渲染后,`todos` 是解构出来的普通数组;后来 `state.todos` 换成了新数组,但模板依赖的 `todos` 仍是旧数组,所以 DOM 不刷新。改成 `const todos = computed(() => state.todos)` 或 `toRefs` 后,问题立刻消失。
那一刻我才真正意识到:响应式追踪不是“数据变了就行”,而是“被模板读取的那个引用必须是响应式的”。
总结
Vue 组件更新后 DOM 不刷新,通常不是 Vue 坏了,而是响应式链路在某个环节断了。排查顺序可以记成:先用 `nextTick` 确认更新时机,再检查响应式 API 和数据声明,接着用 `onRenderTracked` 看依赖,最后排查 `key`、`keep-alive` 和异步竞态。把“数据变了”升级成“响应式数据被模板依赖了”,大多数问题都会现形。
转载请注明出处,版权归原作者所有。
管理员
黑卡会员



