思考:为啥如下代码没有立马看到loading的效果
loading.value = true
tableData.value.push(...data) // 大数据量操作
setTimeout(() => {
loading.value = false
dialogVisible.value = false
}, 2000)
而这样写,可以立马看到loading
loading.value = true
setTimeout(() => {
tableData.value.push(...data)
setTimeout(() => {
loading.value = false
dialogVisible.value = false
}, 0)
}, 0)
这个现象与 JavaScript 的事件循环和 Vue 的响应式更新机制有关:
第一种情况的问题:
- 当你设置
loading.value = true
时,Vue 会标记这个变化,但不会立即更新 DOM。 - 接着执行
tableData.value.push(...data)
,这是一个同步的、耗时的操作(因为数据量大)。 - 在 JavaScript 执行这个耗时操作期间,浏览器的主线程被阻塞,无法进行 UI 渲染。
- 只有当同步代码全部执行完后,浏览器才会进行 UI 更新,此时用户才能看到 loading 状态。
- 但此时已经进入了 setTimeout 的异步流程,很快又会将 loading 设为 false。
第二种情况为什么有效:
loading.value = true
设置后,通过setTimeout(..., 0)
将大数据操作放入任务队列。- 这样浏览器有机会在同步代码执行完后立即进行 UI 渲染,显示出 loading 状态。
- 然后才会执行队列中的大数据操作。
根本原因:
JavaScript 是单线程的,同步的耗时操作会阻塞 UI 渲染。Vue 的响应式更新也需要等到同步代码执行完后才会批量处理。
更好的解决方案:
- 使用 Vue.nextTick:
this.$nextTick().then(() => {
tableData.value.push(...data)
this.$nextTick().then(() => {
loading.value = false
dialogVisible.value = false
})
})