You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue函数执行期间如何更新DOM?加载Spinner不显示求助

问题:耗时函数执行时加载Spinner无法及时显示

我有一个耗时约15秒的函数调用,想要在该函数执行期间显示加载Spinner。我知道DOM要等到函数执行完成后才会更新,除非手动触发。我试过nextTick和async/await,但Spinner始终要等到函数执行完成后才会显示。我故意省略了代码末尾的loading.value = false,验证下来Spinner确实是延迟显示,而非功能异常。

我的UI代码

<template>
  <Background>
    <Card>
      <CardHeader
        title="Materials"
        description="All Current Materials"
        add-button-text="Add Materials"
        add-button-link="/settings/Materials/addMaterial"
      >
      </CardHeader>
      <div v-if="loading">
        <div class="row">
          <div class="col-sm d-flex justify-content-center">
            <ProgressSpinner />
          </div>
        </div>
      </div>
      <button @click="add">Add</button>
    </Card>
  </Background>
</template>

核心逻辑代码

<script setup>
const loading = ref(false);

let femaleStripSpacersNeeded = [];
let spacers = [];
let spacerQty = [];

const getSpacerAlgo = SpacerAlgo();

function add() {
  loading.value = true;
  let stripWidths = [3000, 21000, 3000];
  let stripTotal = [1, 1, 1];

  for (let z = 0; z < stripWidths.length; z++) {
    for (let n = 0; n < stripTotal[z]; n++) {
      let stripWidth = stripWidths[z];
      let femaleStripSpacers = getSpacerAlgo.minSpacers(
        stripWidth,
        spacers,
        spacerQty
      );
      femaleStripSpacersNeeded.push(femaleStripSpacers[0]);
      spacerQty = femaleStripSpacers[1];
    }
  }
}
</script>

尝试过的async/await版本

function add() {
    return new Promise((resolve) => {
      let stripWidths = [3000, 21000, 3000];
      let stripTotal = [1, 1, 1];

      for (let z = 0; z < stripWidths.length; z++) {
        for (let n = 0; n < stripTotal[z]; n++) {
          let stripWidth = stripWidths[z];
          let femaleStripSpacers = getSpacerAlgo.minSpacers(
            stripWidth,
            spacers,
            spacerQty
          );
          femaleStripSpacersNeeded.push(femaleStripSpacers[0]);
          spacerQty = femaleStripSpacers[1];
        }
      }
      resolve("resolved");
    });
  }

async function asyncCall() {
  loading.value = true;
  await add();
  loading.value = false;
}

解决方案

你之前的async/await写法无效,因为Promise内部的代码还是同步阻塞的——设置loading.value = true后,JavaScript引擎立刻执行了Promise里的所有耗时循环,根本没给浏览器留出渲染Spinner的时间。

正确的做法是:让浏览器先完成Spinner的渲染,再执行耗时任务。可以通过以下两种方式实现:

方法1:使用setTimeout将耗时任务推入宏任务队列

<script setup>
const loading = ref(false);

// ... 其他变量定义不变

async function add() {
  loading.value = true;
  // 用setTimeout把耗时任务放到下一个宏任务,让浏览器先渲染DOM
  await new Promise(resolve => setTimeout(resolve, 0));
  
  let stripWidths = [3000, 21000, 3000];
  let stripTotal = [1, 1, 1];

  for (let z = 0; z < stripWidths.length; z++) {
    for (let n = 0; n < stripTotal[z]; n++) {
      let stripWidth = stripWidths[z];
      let femaleStripSpacers = getSpacerAlgo.minSpacers(
        stripWidth,
        spacers,
        spacerQty
      );
      femaleStripSpacersNeeded.push(femaleStripSpacers[0]);
      spacerQty = femaleStripSpacers[1];
    }
  }
  
  loading.value = false; // 任务完成后关闭加载状态
}
</script>

方法2:使用queueMicrotask推入微任务队列

如果不想用setTimeout(0延迟可能有极小的等待时间),可以用queueMicrotask,它会在当前宏任务结束后、下一个宏任务开始前执行,同样能让浏览器先完成渲染:

async function add() {
  loading.value = true;
  await new Promise(resolve => queueMicrotask(resolve));
  
  // ... 耗时循环代码不变
  
  loading.value = false;
}

原理说明

  • 设置loading.value = true后,Vue会标记DOM需要更新,但不会立刻渲染。
  • 通过await一个微任务/宏任务,让JavaScript引擎暂停当前函数执行,把控制权交还给浏览器。
  • 浏览器此时会执行DOM更新,显示Spinner,之后再回到JavaScript执行剩下的耗时任务。

另外,15秒的执行时间过长,建议把耗时的minSpacers计算拆分成多个小任务,用分批次执行的方式,避免长时间阻塞主线程,让页面保持响应。

内容的提问来源于stack exchange,提问作者bobthegoalie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 09:05:58