[英]How To Pass State Update To Custom Render Loop In React's `useEffect`?
I often encounter the situation that I build a window.requestAnimationFrame
based render loop, but want some states to be exchanged with the inside of the loop.我经常遇到这样的情况,我构建了一个基于
window.requestAnimationFrame
的渲染循环,但是想要在循环内部交换一些状态。 There are a few ways to accommplish that, but I'm not sure which on is the best.有几种方法可以做到这一点,但我不确定哪种方法最好。 My setup usually looks something like this:
我的设置通常是这样的:
function Animation({someState}) {
const loopDataRef = useRef();
useEffect(() => {
//do something initialization work for the renderloop, e.g. getContext, etc.
let frame;
const loop = (cts) => {
frame = window.requestAnimationFrame(loop)
//do some loop work using someState and loopDataRef.current
}
loop();
return () => window.cancelAnimationFrame(frame);
}, []);
}
Note that using someState
inside the loop will always use the value from the last ran of the effect, not necessarily the current value.请注意,在循环中使用
someState
将始终使用效果的最后一次运行的值,不一定是当前值。 Here are the some obvious solutions:以下是一些显而易见的解决方案:
I can put someState
in the dependency array of useEffect
, this way the loop will be restarted whenever the state changes.我可以将
someState
放在 useEffect 的依赖数组中,这样只要useEffect
发生变化,循环就会重新启动。 But if the initialization is expensive, for example with WebGL where I create all the textures there and compile the shaders, it doesn't seem very elegant.但是如果初始化很昂贵,例如使用 WebGL,我在那里创建所有纹理并编译着色器,它似乎不是很优雅。
I can use two effects, one for the initialization of the loop itself, and another one for just the loop, which stops and starts on every state change.我可以使用两种效果,一种用于循环本身的初始化,另一种仅用于循环,它在每次 state 更改时停止和启动。 But I still think, ideally an animation loop should run until it stops and not stop/start in between.
但我仍然认为,理想情况下,animation 循环应该一直运行到停止,而不是在其间停止/启动。
Another solution with two effects, but this time I do const someStateRef = useRef()
and then create an effect with someState
in its dependency array which just writes someState
into someStateRef.current
so I can use it inside the loop.另一个具有两种效果的解决方案,但这次我执行
const someStateRef = useRef()
然后在其依赖数组中使用someState
创建一个效果,它只是将someState
写入someStateRef.current
因此我可以在循环中使用它。 Here is an example where I implement this.这是我实现它的示例。 Alternatively one can write
someState
into someStateRef.current
on every render, without another effect.或者,可以在每次渲染时将
someState
写入someStateRef.current
,而不会产生其他影响。 Looks very performant, but not really elegant.看起来非常高效,但不是很优雅。
What's the most React way to do it?最 React 的方式是什么?
I'd go with the 3rd option that you already implemented , as it looks exactly like one example from the React Hooks FAQ (3rd code block):我将 go 与您已经实施的第三个选项,因为它看起来与 React Hooks FAQ(第三个代码块)中的一个示例完全一样:
function Example(props) {
// Keep latest props in a ref.
const latestProps = useRef(props);
useEffect(() => {
latestProps.current = props;
});
useEffect(() => {
function tick() {
// Read latest props at any time
console.log(latestProps.current);
}
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []); // This effect never re-runs
}
(replacing set/clearInterval
with request/cancelAnimationFrame
) (将
set/clearInterval
替换为request/cancelAnimationFrame
)
For completeness: The first two options suffer from the flaws you mentioned, and writing someState
to someStateRef.current
on every render without another effect is not recommended.为了完整起见:前两个选项存在您提到的缺陷,不建议在每次渲染时将
someStateRef.current
写入someState
而没有其他效果。
Avoid reading and updating refs during rendering because this makes your component's behavior difficult to predict and understand.
避免在渲染期间读取和更新引用,因为这会使组件的行为难以预测和理解。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.