React加载动画为什么会出现内容和骨架屏同时闪烁?
我在用React做数据加载时的骨架屏过渡,但发现内容渲染和骨架屏会同时显示0.5秒,导致闪烁问题。之前用条件判断控制显示:
function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('api/products')
.then(res => res.json())
.then(data => {
setProducts(data);
setLoading(false); // 这里直接设置loading为false
});
}, []);
return (
<div>
{loading && <Skeleton count={4} />}
{!loading && <ProductGrid items={products} />}
</div>
);
}
尝试过把loading设为’pending’|’success’状态区分,但切换时还是有重影。有没有更好的方法让过渡更平滑?感觉是状态切换时机没抓准…
你现在的写法逻辑没问题,但问题出在:数据加载完立刻切 loading 为 false,React 同一帧里会把旧的 Skeleton 和新的 ProductGrid 都放进 DOM,浏览器紧接着重绘,就会出现「闪一下」的视觉残留。
更优雅的解法是加一层「过渡延迟」,别让状态一变就立刻切内容,用个最小显示时间,比如至少让 Skeleton 显示 300ms,避免空载时内容突然蹦出来,也避免加载快时骨架屏一闪而过导致用户没感知。
代码这样改:
这样更清晰:Skeleton 显示时间可控,内容不会「抢帧」,用户感知也更平滑。要是加载本身就慢(比如 1s+),那 300ms 延迟几乎无感;要是加载特别快(比如 100ms),也能保证骨架屏给用户一个「正在加载」的心理预期。
顺带一提,如果你用的是 React 18 的
startTransition,也可以把内容渲染包进去做低优更新,不过对这个场景有点杀鸡用牛刀,上面这个方案轻量又实用。可以优化成用 useEffect 监听 loading 状态变化,在 loading 从 true 变成 false 的时候再触发内容渲染。或者更直接一点,用一个布尔值来控制是否已经加载完成,这样可以确保内容只有在数据真正准备好之后才会渲染。
比如你可以这样改:
这样骨架屏隐藏和内容显示就变成两个阶段了,不会同时出现。其实这个方式就是把 loading 状态的切换和内容的渲染拆开控制,确保过渡平滑。