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

Vue3 SPA长任务卡顿:实现先加载视图再注入视图模型

解决思路与代码示例

1. 先渲染骨架屏,延迟执行长任务

核心是让浏览器先渲染无数据的骨架结构,再处理耗时的初始化逻辑。用响应式变量控制骨架和真实内容的切换,同时把init()放到宏任务队列,避免阻塞初始渲染:

<template>
  <!-- 骨架屏 -->
  <div v-if="!isReady" class="skeleton">
    <div class="skeleton-item"></div>
    <div class="skeleton-item"></div>
    <div class="skeleton-item"></div>
  </div>
  <!-- 真实内容 -->
  <div v-else>
    <div v-for="item in data" :key="item.id" class="content-item">{{ item.text }}</div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const isReady = ref(false)
const data = ref([])

// 原来耗时6秒的初始化逻辑
async function init() {
  // 模拟长任务:比如大量数据解析、复杂计算
  await new Promise(resolve => setTimeout(resolve, 6000))
  data.value = Array.from({length: 10}, (_, i) => ({id: i, text: `内容项 ${i+1}`}))
  isReady.value = true
}

onMounted(() => {
  // 用setTimeout把init丢到宏任务,让浏览器先渲染骨架
  setTimeout(() => {
    init().catch(err => console.error('初始化失败:', err))
  }, 0)
})
</script>

<style scoped>
.skeleton-item {
  height: 80px;
  margin: 12px 0;
  border-radius: 6px;
  background: linear-gradient(90deg, #eee 25%, #ddd 50%, #eee 75%);
  background-size: 200% 100%;
  animation: skeleton-loading 1.2s infinite;
}

@keyframes skeleton-loading {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
</style>

2. 拆分长任务,避免主线程卡死

如果init()里是同步计算(比如循环处理上万条数据),直接拆成小批次处理,给浏览器留时间渲染:

async function init() {
  // 先拉取原始数据
  const rawData = await fetch('/api/big-data').then(res => res.json())
  const batchSize = 1500 // 每批处理1500条
  let processedData = []

  for (let i = 0; i < rawData.length; i += batchSize) {
    const batch = rawData.slice(i, i + batchSize)
    // 处理当前批次数据
    processedData.push(...batch.map(item => ({
      id: item.id,
      text: `处理后的${item.content}`
    })))
    // 让浏览器执行渲染队列
    await new Promise(resolve => requestIdleCallback(resolve))
  }

  data.value = processedData
  isReady.value = true
}

3. 用Web Worker彻底隔离长任务

如果拆分任务还是不够,把耗时计算放到Web Worker里,完全不占用主线程:

// 组件内的init函数
async function init() {
  const worker = new Worker('./data-processor.worker.js')
  worker.postMessage({ action: 'process' })

  worker.onmessage = (e) => {
    if (e.data.type === 'done') {
      data.value = e.data.data
      isReady.value = true
      worker.terminate()
    }
  }

  worker.onerror = (err) => {
    console.error('Worker出错:', err)
    worker.terminate()
  }
}
// data-processor.worker.js
self.onmessage = async (e) => {
  if (e.data.action === 'process') {
    // 这里放耗时6秒的计算逻辑
    await new Promise(resolve => setTimeout(resolve, 6000))
    const result = Array.from({length: 10}, (_, i) => ({id: i, text: `Worker处理的内容 ${i+1}`}))
    self.postMessage({ type: 'done', data: result })
  }
}

为啥之前的Async Component没用?

Async Component是用来异步加载组件的代码包(拆分chunk,减少首屏体积),但你的问题是组件已经加载后,内部的长任务阻塞渲染,所以它解决不了这个场景的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:57:45