前端-详解Vue异步更新
目录
一. 什么是异步更新
🌟 什么是“减少不必要的重渲染”?
🚀 “吞吐更高”是啥意思?
🧊 “性能更稳”又是什么鬼?
✨ 总结版:通俗解释
二. 什么时候会“异步”?
三. Vue 是怎么做的?(队列 & 去重 & 微任务)
四. 你需要的“钥匙”:nextTick
五. 一眼看懂的示例
5.1 批处理(batching)与“读旧 DOM”的坑
5.2 列表 & $refs 顺序(你之前就问过这个点!)
六. 和 Watch/Computed 的关系
七. Vue 2 vs Vue 3:调度器差异(重要但不复杂)
八. 高频“为什么”与“怎么做”
九. 实战建议(给你的 Vue2 + Element-UI 项目)
十. 一份迷你清单(面试/复习超好用)
一. 什么是异步更新
异步更新指:当你连续修改 data/props
时,Vue 不会立刻把每一次变更都渲染到 DOM,而是把这些变更合并(batching)后放到“下一个 tick”统一更新。
这样做的好处:减少不必要的重渲染,吞吐更高、性能更稳。
直观理解:你说“+1、+1、+1”,Vue 先做个笔记:“这个值最终要 +3”,等到本轮同步任务结束,再一次性把 DOM 更新好。
什么是重渲染https://blog.csdn.net/weixin_52159554/article/details/151819344?sharetype=blogdetail&sharerId=151819344&sharerefer=PC&sharesource=weixin_52159554&spm=1011.2480.3001.8118
🌟 什么是“减少不必要的重渲染”?
假设你写了这样的代码:
this.count++
this.name = 'ChatGPT'
this.age = 3000
如果 Vue 每次属性变了就立刻更新 DOM,那这段代码可能会触发 3次 DOM 更新操作!
那浏览器得累死,性能嗷嗷掉啊 😫
但 Vue 采用“异步更新+批处理”,它会把这三次数据变更先缓存起来,然后在当前同步代码执行完毕后**,只进行一次 DOM 更新。
🎯 所以:
👉 少了两次无谓的 DOM 更新,性能一下就上去了!
🚀 “吞吐更高”是啥意思?
“吞吐量”(throughput)是程序处理大量请求、事件的能力。
Vue 一次处理多个变更,只更新一次 DOM,相当于:
-
减少了资源浪费
-
提高了单位时间内能处理的“更新请求”数量
🎯 所以:
👉 系统能“吞”下更多变更,反应也更快!
🧊 “性能更稳”又是什么鬼?
假设你在一个高频率交互场景下,比如:
window.addEventListener('mousemove', () => {this.x++this.y++
})
如果每次鼠标动一下都立刻重渲染 DOM,页面可能会卡顿、掉帧。
但是有了异步更新机制:
-
Vue 会等这一批操作处理完,再一起更新 DOM
-
页面流畅度明显更好,性能更稳定
🎯 所以:
👉 避免频繁、琐碎的 DOM 改动带来的性能抖动,用户体验更平滑!
✨ 总结版:通俗解释
Vue 的异步更新机制就像是外卖骑手送餐时攒单派送:
-
你点了三杯奶茶(数据更新)
-
外卖员不立刻跑三次,而是等你点完,一次性送过去(下一 tick 批量更新)
-
节省人力、时间(CPU、内存),你拿得快,他跑得轻松,双赢!
二. 什么时候会“异步”?
-
大多数同步代码上下文里(例如事件回调、生命周期钩子、普通函数体内)对响应式数据的修改,都会在同一 tick 被合并,随后统一渲染。
-
也正因为如此,你在修改完数据的那一行代码后立刻读 DOM,读到的还是旧 DOM。
三. Vue 是怎么做的?(队列 & 去重 & 微任务)
-
队列(queue):每次数据变更会触发相关 watcher/component 的“更新任务”加入队列。
-
去重(dedupe):相同的 watcher 只会入队一次(用 id 去重),多次修改合并为一次更新。
-
何时刷新:尽可能用“微任务(microtask)”在下一个 tick刷新队列(
Promise.then
等),环境不支持时用宏任务做兜底(如setTimeout
)。 -
渲染顺序(简化):
-
计算/侦听变更入队 → 2) 下一 tick 刷新队列 → 3) 执行渲染/patch → 4) 触发“更新后”的回调(如
updated
、$nextTick
回调)。
-
四. 你需要的“钥匙”:nextTick
修改数据后,如需读取更新后的 DOM或依赖最新布局做计算,用它就对了。
Vue 2:
// 回调式
this.$nextTick(() => {// 这里 DOM 已经是最新了
});// Promise 式
Vue.nextTick().then(() => {// DOM 已更新
});
Vue3:
import { nextTick } from 'vue';await nextTick();
// 这里 DOM 已更新
小口诀:改完数据要看 DOM?nextTick!
五. 一眼看懂的示例
5.1 批处理(batching)与“读旧 DOM”的坑
data() { return { n: 0 }; },
methods: {add() {this.n++;this.n++;console.log('此时数据 n=', this.n); // 2(数据是新的)const el = this.$refs.box;console.log('此时 DOM 文本=', el.textContent); // 仍然是旧的(比如还显示0)this.$nextTick(() => {console.log('nextTick 后 DOM 文本=', el.textContent); // 这时才会是 2});}
}
5.2 列表 & $refs
顺序(你之前就问过这个点!)
<li v-for="todo in todos" :key="todo.id" ref="rows">{{ todo.text }}</li>
<button @click="prepend">在最前插一条</button>
methods: {prepend() {this.todos.unshift({ id: Date.now(), text: 'X' });// 立刻读 $refs.rows:还是旧的顺序console.log(this.$refs.rows.map(li => li.textContent.trim()));this.$nextTick(() => {// 这里才是新顺序console.log(this.$refs.rows.map(li => li.textContent.trim()));});}
}
你那句
const txts = this.$refs.rows.map(li => li.textContent.trim());
的作用:把所有 li 的文本取出来形成一个字符串数组。但要“取新顺序”,记得放到$nextTick
里!
六. 和 Watch/Computed 的关系
-
Computed:有缓存,依赖变更才重新求值;多次依赖变更仍被合并到一次更新。
-
Watch:数据变了会触发回调;同一 tick 内多次变更可能被合并为一次回调(取决于实现时机)。
-
Vue 3里
watch
还能指定flush
时机(见下一节)。
七. Vue 2 vs Vue 3:调度器差异(重要但不复杂)
-
共同点:都倾向于用微任务队列合并更新,
nextTick
语义一致。 -
Vue 3 新增能力:
watch
/watchEffect
可指定回调执行相对渲染的时机:-
flush: 'pre'
(默认):在组件渲染之前执行(同一 tick 内),适合驱动渲染的逻辑。 -
flush: 'post'
:渲染之后执行,天然等价于“带 nextTick 的 watch”,适合读 DOM。 -
flush: 'sync'
:同步立即执行(不排队),谨慎使用,因为可能导致更多渲染。
-
watch(() => state.value, () => {// 默认 pre:这里的 DOM 还没更新
});watch(() => state.value, () => {// 这里 DOM 已经更新,相当于自动 nextTick
}, { flush: 'post' });
八. 高频“为什么”与“怎么做”
Q1:为什么我改了数据,DOM 没变?
A:因为更新是异步批处理;在同一 tick 里 DOM 还没 patch。用 nextTick
读最新 DOM。
Q2:我连续改了多次,为什么只渲染了一次?
A:这是去重合并在发挥作用,性能更好。
Q3:我能强制同步更新吗?
A:不建议。Vue 2 有 Vue.config.async
(仅用于调试,生产不推荐);Vue 3 有 flush: 'sync'
的 watch,但要非常谨慎,容易拖慢性能/引发级联更新。
Q4:updated
钩子 vs nextTick
?
A:updated
在组件自身更新后触发,适合做与该组件 DOM相关的收尾;nextTick
更灵活:改完数据立刻就能等待“全局” DOM 更新完成,常用于任意位置的“读新 DOM”。
九. 实战建议(给你的 Vue2 + Element-UI 项目)
-
所有需要“读 DOM”的逻辑,改数据后包一层
this.$nextTick
,包括:-
读
$refs
(长度、顺序、尺寸) -
计算滚动高度、图表容器尺寸(ECharts 重绘)
-
依赖布局的动画起始值
-
-
批量修改数据时,不要在中间穿插“读 DOM”——统一改完再读一次,减少闪烁。
-
复杂联动用队列思维重构:把“改 A 触发 B、改 B 又触发 C”的链条,设计成“本 tick 统一决策 → 下个 tick 一次 patch”,避免抖动。
十. 一份迷你清单(面试/复习超好用)
-
Vue 会把同一 tick 内的多次数据变更合并为一次 DOM 更新
-
刷新采用微任务优先,
nextTick
用来等待 DOM 完成更新 -
读 DOM 放 nextTick,否则大概率读到旧值
-
Vue 2 主要靠 watcher 队列 + 去重;Vue 3 有通用 job queue +
flush
选项 -
仅在必须时考虑同步(
flush: 'sync'
等),慎用!