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
相关产品推荐
相关产品推荐

