React加载动画为什么会出现内容和骨架屏同时闪烁?

司空晴文 阅读 86

我在用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’状态区分,但切换时还是有重影。有没有更好的方法让过渡更平滑?感觉是状态切换时机没抓准…

我来解答 赞 14 收藏
二维码
手机扫码查看
2 条解答
Prog.诺曦
你这个问题我太熟了,之前踩过坑,骨架屏和内容同时闪其实不是状态没抓准,是 React 的渲染时机和浏览器重绘节奏没对上。

你现在的写法逻辑没问题,但问题出在:数据加载完立刻切 loading 为 false,React 同一帧里会把旧的 Skeleton 和新的 ProductGrid 都放进 DOM,浏览器紧接着重绘,就会出现「闪一下」的视觉残留。

更优雅的解法是加一层「过渡延迟」,别让状态一变就立刻切内容,用个最小显示时间,比如至少让 Skeleton 显示 300ms,避免空载时内容突然蹦出来,也避免加载快时骨架屏一闪而过导致用户没感知。

代码这样改:

function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [showSkeleton, setShowSkeleton] = useState(true);

useEffect(() => {
let timeoutId;
let isMounted = true;

const fetchData = async () => {
try {
const res = await fetch('api/products');
const data = await res.json();
if (!isMounted) return;

setProducts(data);

// 先设 loading 为 false,但别急着关 Skeleton
setLoading(false);

// 确保 Skeleton 至少显示 300ms
timeoutId = setTimeout(() => {
if (isMounted) setShowSkeleton(false);
}, 300);
} catch (e) {
console.error(e);
setLoading(false);
setShowSkeleton(false);
}
};

fetchData();

return () => {
isMounted = false;
clearTimeout(timeoutId);
};
}, []);

return (

{showSkeleton && }
{!showSkeleton && }

);
}


这样更清晰:Skeleton 显示时间可控,内容不会「抢帧」,用户感知也更平滑。要是加载本身就慢(比如 1s+),那 300ms 延迟几乎无感;要是加载特别快(比如 100ms),也能保证骨架屏给用户一个「正在加载」的心理预期。

顺带一提,如果你用的是 React 18 的 startTransition,也可以把内容渲染包进去做低优更新,不过对这个场景有点杀鸡用牛刀,上面这个方案轻量又实用。
点赞 2
2026-02-25 07:03
司徒素红
这其实是渲染时机的问题,你现在的逻辑是加载完数据后立刻隐藏骨架屏并渲染内容,但 React 的渲染和 DOM 更新是异步的,所以会有一瞬间两个组件都存在。

可以优化成用 useEffect 监听 loading 状态变化,在 loading 从 true 变成 false 的时候再触发内容渲染。或者更直接一点,用一个布尔值来控制是否已经加载完成,这样可以确保内容只有在数据真正准备好之后才会渲染。

比如你可以这样改:

function ProductList() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [renderContent, setRenderContent] = useState(false);

useEffect(() => {
fetch('api/products')
.then(res => res.json())
.then(data => {
setProducts(data);
setLoading(false);
setRenderContent(true);
});
}, []);

return (

{loading && }
{renderContent && }

);
}


这样骨架屏隐藏和内容显示就变成两个阶段了,不会同时出现。其实这个方式就是把 loading 状态的切换和内容的渲染拆开控制,确保过渡平滑。
点赞 5
2026-02-05 11:01