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. 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. 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. 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.
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. But I still think, ideally an animation loop should run until it stops and not stop/start in between.
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. Here is an example where I implement this. Alternatively one can write someState
into someStateRef.current
on every render, without another effect. Looks very performant, but not really elegant.
What's the most React way to do it?
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):
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
)
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.
Avoid reading and updating refs during rendering because this makes your component's behavior difficult to predict and understand.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.