Published on

React useCallbackの使い方と使い所

Authors
  • avatar
    Name
    ssu
    Twitter

ReactのuseCallbackの役割とuseCallbackをいつ使えば良いのかを紹介したいと思います。

主にuseCallbackは``useCallback(fn, deps)`で定義され、despが変わらない限り、新しい関数のインスタンス(fn)を作りません。これだけだと、なんのために存在しているかわからないと思います。

そこで、useCallbackが主に解決している問題を紹介します。 Memo化したコンポーネントが親で作成した関数をpropsとして指定しているばかりに、Memo化したにも関わらず、親が再描画されたときに子コンポーネントも意図せずに再描画されてしまいパフォーマンスが低下する問題があります。これを解決するのがuseCallbackの主な役割です。

例えば、下記のようにReact.memoを使って、コンポーネントをメモ化しているとします。 (※ React memoの使い方と使い所はこちらを参照)

ただ、下記のような場合だとせっかくメモ化しても、countの変数が変わるたびにAppが再度実行され、その際のconst getFavoritFoodも実行され新しいインスタンスができ、新しいインスタンスのため、 FavoriteFoodのプロパティが新しくなるため、FavoriteFoodが再描画されてしまいます。 ( countが変わるたびに console.log('get favorite function is called')を確認できるので見てみてください)

このようにjavascriptでは、functionはobjectでありfunctionを定義すると新しいインスタンスを作り出します。 そして、新しいインスタンスであるので、それを参照している子コンポーネントが影響を受けてしまいます。

このような親のコンポーネントが再描画された時に、functionの新しいインスタンスが作成されるのを防ぐ役割を担うのがuseCallbackです。useCallbackuseCallback( () => {}, deps)で定義され、despが変わらない限り、新しい関数のインスタンスを作りません。

そうすることで、今回のような例のケースでもuseCallbackでgetFavoriteFoodをラップすることで、メモ化が意図した通りに動くようになります。

// React.memoが正常に動作していない例 import React, { useState } from "react"; const FavoriteFood = React.memo(({food}) => { const [msg, setMessage] = useState('') console.log('FavoritFood comp is called') return ( <> <p>好きな食べ物は: {msg}</p> { msg ? ( <button onClick={ () => setMessage('') }>隠す</button> ) : ( <button onClick={ () => setMessage(food) }>好きな食べ物を表示する</button> ) } </> ) }) const App = () => { const [count, setCount] = useState(0) const getFavoritFood = () => { console.log('get favorite function is called') return "りんご" } console.log("App is called") return ( <> <p>{count}</p> <button onClick={() => setCount(count + 1)}>++</button> <FavoriteFood food={getFavoritFood} /> </> ); } export default App;

useCallbackを用いて、メモ化を意図通りに動くようにするためには、下記のようにコードを変更したらAppコンポーネントが再描画されても、getFavoriteFoodは再度実行されないので、FavoriteFoodコンポーネントが実行されなくなり、描画の最適化ができるようになります。 ( countが変わるたびに console.log('get favorite function is called')が表示されなく、FavoriteFoodコンポーネントが描画されるときのみに実行されるのがわかるかと思います。)

// useCallbackを用いることで、React.memoが正常(意図した通り)に動作している例 import react, { usecallback, usestate } from "react"; const favoritefood = react.memo(({food}) => { const [msg, setmessage] = usestate('') console.log('favoritfood comp is called') return ( <> <p>好きな食べ物は: {msg}</p> { msg ? ( <button onclick={ () => setmessage('') }>隠す</button> ) : ( <button onclick={ () => setmessage(food) }>好きな食べ物を表示する</button> ) } </> ) }) const app = () => { const [count, setcount] = usestate(0) const getfavoritfood = usecallback(() => { return "りんご" },[]) console.log("app is called") return ( <> <p>{count}</p> <button onclick={() => setcount(count + 1)}>++</button> <favoritefood food={getfavoritfood} /> </> ); } export default app;

このように、function自体の参照を保存しておき、 新しいインスタンスを無駄に作らないことで、その関数を使っているコンポーネントが 無駄に再度描画されるのを防ぐのがuseCallbackになります。

useCallbackとuseMemoは似ているので、違いが気になる場合は、useMemoの使い方と使い所を参考にしてみてください。

参考: React memoの使い方と使い所

参考: useMemoの使い方と使い所

参考: Your Guide to React.useCallback()

参考: What's the difference between useCallback and useMemo in practice?