用 useState 做动画卡顿,该怎么优化?

慕容鑫哲 阅读 18

我在 React 里用 useState 控制一个元素的位置做简单动画,但一动就掉帧,特别卡。明明只是改个 left 值,为啥这么慢?

试过用 requestAnimationFrame 包裹 setState,但没效果。是不是不该用状态驱动动画?

const [position, setPosition] = useState(0);

useEffect(() => {
  const id = setInterval(() => {
    setPosition(prev => prev + 1);
  }, 16);
  return () => clearInterval(id);
}, []);

return <div style={{ position: 'absolute', left: position }}>移动块</div>;
我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
夏侯怡彤
问题应该出在每次 setState 都触发了 React 的完整渲染流程——组件重新 render、diff、更新 DOM,而动画帧率要求的是每帧都得在 16ms 内完成,React 的调度根本扛不住这么高频的更新,尤其当组件树稍微复杂点,卡顿就更明显了。

你试了 requestAnimationFrame 包裹 setState,但其实没解决根本问题:你还是在每一帧里调用了 setState,React 依然要走一遍生命周期,只是节奏变准了而已,没变快。

真正该做的是绕过 React 的渲染机制,直接操作 DOM,或者用纯 CSS 动画。比如你这个场景,用 requestAnimationFrame + ref 操作 DOM 属性是最轻量的方案:

const divRef = useRef(null);

useEffect(() => {
let position = 0;
let animationId;

const animate = () => {
position += 1;
if (divRef.current) {
divRef.current.style.left = position + 'px';
}
animationId = requestAnimationFrame(animate);
};

animationId = requestAnimationFrame(animate);

return () => cancelAnimationFrame(animationId);
}, []);


或者更狠一点——直接用 CSS transition:

const [targetLeft, setTargetLeft] = useState(0);

useEffect(() => {
const id = setInterval(() => {
setTargetLeft(prev => prev + 1);
}, 16);
return () => clearInterval(id);
}, []);




加个 transition: none 是为了防止 React 更新时意外触发浏览器的重排/重绘优化逻辑,但其实更推荐直接用 transform 动画:

const [offset, setOffset] = useState(0);

useEffect(() => {
let frame;
const loop = () => {
setOffset(v => v + 1);
frame = requestAnimationFrame(loop);
};
frame = requestAnimationFrame(loop);
return () => cancelAnimationFrame(frame);
}, []);


translateX(${offset}px) }}>

注意用 transform 而不是 left,因为 transform 是 GPU 硬件加速的,不会触发重排,性能好很多。

总结一句:状态驱动高频动画是反模式,React 不是动画引擎,别拿它当定时器用。动效类逻辑,要么直接操作 DOM,要么用 CSS,要么上专业库比如 Framer Motion、GSAP,别硬刚 setState。
点赞 3
2026-02-26 14:09