Vue3 nextTick 与 deep:true 详解笔记
vue前端代码样例
js
//监听两个响应式数据的变化:
watch(
//filteredChartData: 过滤后的图表数据
//() => chartData.value: 原始图表数据(使用 getter 函数形式)
[filteredChartData, () => chartData.value],
() => {
//只有在当前标签页为 "stats"(售卖数量统计)时才执行渲染
if (activeTab.value !== "stats") return;
nextTick(() => { renderChart(); chartInstance?.resize(); });
},
//启用深度监听,能够检测对象内部属性的变化
{ deep: true }
);
一、nextTick 到底是干什么的?
Vue 的更新机制
Vue 的响应式数据变化后:
js
count.value++
并不是:
txt
数据变化 → DOM立刻更新
而是:
txt
数据变化
↓
Vue记录变化
↓
放入更新队列
↓
统一批量更新DOM
这是 Vue 的性能优化机制。
否则:
js
count.value++
count.value++
count.value++
如果每次都更新 DOM:
txt
1 -> 2 -> 3
性能会非常差。
二、nextTick 的本质
nextTick 的作用
txt
等待 Vue 把 DOM 更新完成
之后再执行代码。
三、现实世界类比(nextTick)
点外卖流程
txt
下单(修改响应式数据)
↓
商家做饭(Vue计算更新)
↓
骑手配送(DOM更新)
↓
收到外卖(页面真正变化)
不能:
txt
刚下单就开门拿外卖
因为外卖还没送到。
四、为什么有时候必须 nextTick?
因为:
txt
数据更新了 ≠ DOM 已更新
例如:
js
show.value = true;
const el = document.getElementById("box");
console.log(el);
你以为:
txt
show=true
↓
div出现
↓
获取到元素
实际上:
txt
show=true
↓
Vue只是记录变化
↓
同步代码继续执行
↓
获取DOM
↓
DOM还没更新
↓
null
五、正确使用 nextTick
js
show.value = true;
nextTick(() => {
const el = document.getElementById("box");
console.log(el);
});
流程:
txt
数据变化
↓
Vue更新DOM
↓
nextTick回调执行
↓
获取最新DOM成功
六、你的 renderChart 为什么需要 nextTick?
你的代码:
js
nextTick(() => {
renderChart();
chartInstance?.resize();
});
这是 ECharts 场景里的经典用法。
原因
ECharts 依赖:
txt
真实DOM宽高
如果 DOM 还没渲染完成:
txt
容器宽高可能还是0
结果会出现:
- 图表空白
- 图表大小异常
- resize失效
- 图表宽高错误
七、什么时候不需要 nextTick?
如果只是处理 JS 数据:
js
watch(count, () => {
console.log(count.value);
});
不涉及 DOM:
txt
不需要 nextTick
八、什么时候必须 nextTick?
只要:
txt
依赖最新DOM状态
通常就需要。
常见必须使用 nextTick 的场景
| 场景 | 是否需要 nextTick |
|---|---|
| 获取元素宽高 | ✅ |
| 初始化 ECharts | ✅ |
| resize 图表 | ✅ |
| focus 输入框 | ✅ |
| 滚动到底部 | ✅ |
| 获取 v-if 创建的元素 | ✅ |
| 纯数据计算 | ❌ |
九、deep:true 是什么?
你的代码:
js
watch(
[filteredChartData, () => chartData.value],
() => {},
{ deep: true }
)
Vue 默认监听机制
Vue 默认:
txt
只监听"引用是否变化"
不监听对象内部属性变化。
十、举例说明 deep
默认情况
js
const user = ref({
name: "张三",
age: 18
});
监听:
js
watch(user, () => {
console.log("变化了");
});
修改内部属性
js
user.value.age = 20;
结果:
txt
watch 不触发
因为:
txt
对象引用没变
Vue认为:
txt
还是同一个对象
十一、加 deep:true 后
js
watch(user, () => {
console.log("变化了");
}, {
deep: true
});
现在:
js
user.value.age = 20;
结果:
txt
watch 触发成功
因为:
txt
deep:true 会递归监听对象内部所有属性
十二、现实世界类比(deep)
不加 deep
Vue像:
txt
只检查文件夹是不是换了
加 deep
Vue像:
txt
连文件夹里的文件内容变化也检查
十三、为什么 deep:true 有性能问题?
因为:
txt
它会递归遍历整个对象
如果对象非常大:
- 大表格
- 大JSON
- 树结构
- 深层嵌套对象
会:
txt
非常耗性能
十四、你这里为什么需要 deep:true?
你的:
js
filteredChartData
大概率是:
js
[
{ name: "A", value: 10 }
]
如果:
js
filteredChartData.value[0].value = 20
数组引用没变。
不加:
js
deep:true
可能监听不到。
十五、Vue3 一个容易误解的点
很多人以为:
txt
响应式 = watch一定能监听
其实不是。
响应式
只是:
txt
数据可追踪
watch
默认:
txt
只监听引用变化
这是两回事。
十六、你的代码完整执行流程
js
watch(
[filteredChartData, () => chartData.value],
() => {
if (activeTab.value !== "stats") return;
nextTick(() => {
renderChart();
chartInstance?.resize();
});
},
{ deep: true }
);
第一步
数据变化:
txt
filteredChartData变化
或者
chartData变化
第二步
watch 被触发。
第三步
判断当前 tab:
js
activeTab.value !== "stats"
如果不是统计页:
txt
直接return
避免无意义渲染。
第四步
nextTick:
txt
等待DOM更新完成
因为:
txt
图表容器可能刚刚显示
第五步
重新渲染 ECharts:
js
renderChart()
第六步
重新计算图表大小:
js
chartInstance?.resize()
十七、最终总结(核心记忆)
nextTick
本质
txt
等待DOM更新完成后执行
核心场景
txt
依赖最新DOM状态
deep:true
本质
txt
深度监听对象内部属性变化
核心场景
txt
监听对象/数组内部属性变化
十八、口诀记忆(面试级)
Vue 更新机制
txt
改数据
↓
异步批量更新DOM
↓
nextTick之后DOM才是最新
watch 默认行为
txt
默认只监听引用变化
deep:true
txt
递归监听内部属性
nextTick 口诀
txt
只要依赖DOM最新状态
就考虑nextTick
deep:true 口诀
txt
对象里面套对象
想监听内部变化
就deep