简体   繁体   中英

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. 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.

source

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM