为何setState在动画帧中被同步调用?React状态更新异常
动画帧内调用setState引发的同步执行与Effect异常问题
我在动画帧(animation frame)内调用setState,原本预期它会像常规情况一样进入队列等待执行,但实际它却立即同步执行。更严重的是,若存在待处理的effect,这些effect也会被立即解析,导致effect的清理函数在动画帧执行过程中就被调用,使得我请求的下一帧无法被清理。观察示例中的计时器,会发现它的速度在不断加快。
示例代码
JavaScript
let handling = false let accum = 0 function MyComponent() { const [state, setState] = React.useState(0) React.useEffect(() => { let frame let time = undefined function handle(t) { handling = true frame = undefined if (time != null) { // 这里只是为了演示,避免React性能限制影响效果 accum += t - time setState(Math.floor(accum / 1000)) } time = t frame = window.requestAnimationFrame(handle) handling = false } if (handling) console.log('oh no!') frame = window.requestAnimationFrame(handle) return () => window.cancelAnimationFrame(frame) }) React.useEffect(() => { window.setInterval(() => { setState(x => x + 0.0001) }, 111) }, []) return <div>{Math.round(state)}</div> } ReactDOM.render(<MyComponent/>, document.querySelector('.root'))
HTML
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <div class="root"></div>
临时规避方案
我知道可以修改为如下代码来避免在不应该的时候请求新帧,但这类繁琐的代码理应可以规避:
let frame let time = undefined let effectIsOver = false function handle(t) { handling = true frame = undefined if (time != null) { setState(s => s + t - time) } time = t if (!effectIsOver) frame = window.requestAnimationFrame(handle) handling = false } frame = window.requestAnimationFrame(handle) return () => { window.cancelAnimationFrame(frame) effectIsOver = true }
内容的提问来源于stack exchange,提问作者riv
相关产品推荐
相关产品推荐

