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遊びがやりやすくましたね✊🏻