页面的loading没有立即生效的引申了解vue的更新机制和js事件循环(1)

20 阅读1分钟

思考:为啥如下代码没有立马看到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 的响应式更新机制有关:

第一种情况的问题:

  1. 当你设置 loading.value = true 时,Vue 会标记这个变化,但不会立即更新 DOM。
  2. 接着执行 tableData.value.push(...data),这是一个同步的、耗时的操作(因为数据量大)。
  3. 在 JavaScript 执行这个耗时操作期间,浏览器的主线程被阻塞,无法进行 UI 渲染。
  4. 只有当同步代码全部执行完后,浏览器才会进行 UI 更新,此时用户才能看到 loading 状态。
  5. 但此时已经进入了 setTimeout 的异步流程,很快又会将 loading 设为 false。

第二种情况为什么有效:

  1. loading.value = true 设置后,通过 setTimeout(..., 0) 将大数据操作放入任务队列。
  2. 这样浏览器有机会在同步代码执行完后立即进行 UI 渲染,显示出 loading 状态。
  3. 然后才会执行队列中的大数据操作。

根本原因:

JavaScript 是单线程的,同步的耗时操作会阻塞 UI 渲染。Vue 的响应式更新也需要等到同步代码执行完后才会批量处理。

更好的解决方案:

  1. 使用 Vue.nextTick
this.$nextTick().then(() => {
  tableData.value.push(...data)
  this.$nextTick().then(() => {
    loading.value = false
    dialogVisible.value = false
  })
})