ReactでrequestAnimationFrameを使う

ふと自分で遊んでる時に、ReactでCanvas触るのどうするんだろなーという気持ちになったので調べた。

ここを読めばすべてのことが書いてある。
https://css-tricks.com/using-requestanimationframe-with-react-hooks/

DEMO

https://codesandbox.io/s/mystifying-framework-hdk7k

具体的に

基本的にrequestAnimationFrameを使う場合は、再帰的に呼び出されるような処理のことが多い。有限なのであればいいが、例えばcanvasで背景アニメーションを描画するなどして永遠に描画させ続けるような場合もある。 そういう場合、例えばもらうpropsが変わって再描画されるたびに、追加であたらしいrequestAnimationFrameのループが始まるようだと困る。 そういう場合はcancelAnimationFrameでcancelしてから、新しいrequestAnimationFrameのループを始めたい👀

useRefを使えばよろしい

useRefは何もDOMにアクセスするためだけにあるわけではなくって、再描画されても同じ値を保持し続けたいような場合に使える。
なので、戦略的には、useRefにrequestAnimationFrameを格納し続けておいて、任意のタイミングでそれをcancelAnimationFrameするということになる。

const Canvas: React.FC = () => {
  const animationRef = useRef(null);
  const animate = () => {
    animationRef.current = requestAnimationFrame(animate)
  };
  
  // なんかのタイミングで
  cancelAnimationFrame(animationRef.current);
}

まぁ、いわゆるなんかのタイミングっていうのは、このコンポーネントが破棄されたとき、となるわけなので、

const Canvas: React.FC = () => {
  const animationRef = useRef(null);
  const animate = () => {
    animationRef.current = requestAnimationFrame(animate)
  };
  useEffect(() => {
    animationRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(animationRef.current);
  }, [])
}

のようにuseEffectを使ってやると、コンポーネントが破棄されたときにcancelAnimationFrameが呼ばれるようになる。

そういうわけでrequestAnimationFrameを使う処理をCustorm Hooksに切り出してやる

const useAnimationFrame = () => {
  const animationRef = useRef(null);
  const animate = () => {
    animationRef.current = requestAnimationFrame(animate)
  };
  useEffect(() => {
    animationRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(animationRef.current);
  }, [])
};

const Canvas: React.FC = () => {
  useAnimationFrame()
}

これで、useAnimtionFrameをつかって何かしらをすることができるようになったが、実際のrequestAnimtaionFrameで回したい処理をどの様に書くかかという点がある🤔

requestAnimtionFrameで回す処理をもらう

どうすべきかなーとか思っていたけど、useAnimationFrameはAnimationFrameについて関心を持っているべきで、それ以外のことは知っておくべきではない。
それ以外のことについては使う側が勝手にやってくれよな✊🏻

というわけで、requestAnimationFrameで関数を受け取るように変更する🚀  

const useAnimationFrame = (callback: () => void) => {
  const requestRef = useRef<ReturnType<typeof requestAnimationFrame>>();
  
  // callback関数に変更があった場合のみanimateを再生成する
  const animate = useCallback(() => {
    callback();
    requestRef.current = requestAnimationFrame(animate);
  }, [callback]);
  
  // callback関数に変更があった場合は一度破棄して再度呼び出す
  useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => {
      if (requestRef.current) {
        return cancelAnimationFrame(requestRef.current);
      }
    };
  }, [animate]);
};

const Canvas: React.FC = () => {
  useAnimationFrame(() => {
    // なんかやりたい処理
  })
}

まとめ

  • useRefはコンポーネントの再描画に関わらず、とっておきたい値を入れるのに向いている
    • elementの参照以外にも色々活用法があるぞ!

これでReactでCanvas遊びがやりやすくましたね✊🏻

Good Canvas & React Lifeを🎍