骨架屏是否闪烁取决于DOM生命周期管理而非<Suspense>本身:需服务端直出骨架HTML、用replaceChildren原子替换、opacity过渡、依据数据存在与否而非loading状态控制显隐。
Vue 3 的 <Suspense> 本身不直接控制骨架屏切换是否闪烁,真正决定“闪不闪”的是骨架结构如何存在、何时替换、以及怎么替换。关键不在 <Suspense> 写法本身,而在它包裹的异步组件内部——尤其是骨架与真实内容的 DOM 生命周期管理。
如果骨架是在 JS 加载后动态创建(比如在 setup() 里用 document.createElement 插入),用户会看到「空白 → 骨架 → 内容」三段式跳变。这是最典型的闪烁根源。
index.html 的对应容器内,例如:<div id="app"><div class="skeleton-post"></div></div>
mounted 或 onMounted 里主动移除骨架的逻辑——它本就不该被“插入”,而应一直存在直到被替换切换时用 innerHTML = '' 或 v-if 控制显隐,都会触发 layout shift,造成“咔哒一跳”。replaceChildren() 是现代浏览器原生支持的安全替换方式。
data-testid 等属性对齐el.replaceChildren(...newContentNodes),而非清空再 appendwhile (el.firstChild) el.removeChild(el.firstChild) + el.append(...) 模拟,但需注意 focus 状态丢失display: none 会让元素退出文档流,visibility: hidden 无法触发动画;只有 opacity 能兼顾视觉平滑与布局稳定。
opacity: 0.8 + transition: opacity 0.25s ease-out
opacity: 1,并移除所有 skeleton 类名will-change: opacity,低端设备上反而增加合成层开销用 loading === true 控制骨架显隐,是典型反模式。它会导致 SSR hydration 后骨架闪一下,或 keep-alive 页面缓存数据后骨架永远不消失。
data === null || data.length === 0,而非依赖 loading 标志onMounted 里补充判断 document.readyState === 'complete',防止服务端已渲染却重复显示骨架