結論
別コンポーネントにpropsで渡す関数はuseCallbackを使い、コンポーネント内で計算結果を値として使う関数に対してはuseMemoを使う。
前提
「useMemoは値を返す。useCallbackは関数を返す。」という前提を押さえておくことが重要。
useMemo
メモ化された値を返すフックです。計算結果を保持しそれを再利用する手法のこと。コンポーネントの再描画時の値の再計算の無駄がなくなりパフォーマンスが上がります。
ちなみに、useMemoはmemoと違ってあらゆる値をメモ化することが可能なので極端な話jsxなどもメモ化することができます。
構文
1 |
useMemo(メモ化する値を計算するコールバック関数,その計算が依存する変数) |
第一引数はコールバック関数を指定しますが、「コールバック関数自体」をメモ化しているのではなく「コールバック関数が返す値」をメモ化します。
使い所
例えば、値を更新する処理だけで呼んでほしい関数があった場合に、入力処理まで呼ばれるようになっている場合など。
実装例
bai関数は関係のないCount2の値が更新される場合は呼ばれません。(useEffectなどで書くと呼ばれる度に描画されることになります。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import React,{useState,useMemo} from'react'; function App(){ const [count1,setCount1] = useState(0); const [count2,setCount2] = useState(0); const bai = count => { return count*2; }; const baiCount = useMemo(() => bai(count1), [count1]); return( <div> <p>Count1:{count1}</p> <p>DubbleCount:{baiCount}</p> <button onClick={()=>setCount1(count1+1)}>increment1</button> <p>Count2:{count2}</p> <button onClick={()=>setCount2(count2+1)}>increment2</button> </div> ); } export default App; |
useCallback
関数を子コンポーネントに渡した際、親コンポーネントがレンダリングされると子のpropsが別物とみなされて子も再レンダリングされてしまいます。そんな場合でもメモ化したい際に使えるフックです。
React.memoと組み合わせる。
メモ化されたコールバック関数を返すフックです。生成したコールバック関数を保持できるためコンポーネントの再描画時に関数を再利用できます。
アロー関数で書いた関数はレンダリングのたびに毎回違う関数を生成します。なので、propsで関数を渡した際に毎回値が変わっていると判定されてしまって、memo化したとしても再レンダリングが行われることになります。
React.memoと併用すると、React.memoでメモ化したコンポーネントにuseCallbackでメモ化したコールバック関数をPropsとして渡すことで、コンポーネントの不要な再描画をスキップすることができパフォーマンスが上がります。
カスタムフックでは適用が必須
なお、カスタムフックで関数を定義する場合は基本的にuseCallbackを適用することが推奨されます。なぜならプロジェクトのさまざまな箇所で利用し、あるコンポーネントがpropsで関数を渡してないとしても別コンポーネントでは渡す可能性があるためです。
構文
第一引数の関数がコールバック関数になりメモ化されます。
1 |
useCallback(コールバック関数(メモ化),[依存しているstate]) |
第二引数(依存配列)
第二引数が空配列になっていた場合は初回レンダリングに生成した関数をReact内で保持するという意味になります。
もし依存しているstateがある場合は指定するとそのstateが更新されるたびに関数が再生成されます。指定しないとずっと同じstateを保持し続けてしまうので注意です。
実装例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import React,{useState,useCallback,useEffect,memo} from'react'; const Button = memo(({id,onClick})=>{ useEffect(()=>{console.log(`render${id}`);}); return <button onClick={onClick}>{id}</button>; }); function App(){ const [count1,setCount1] = useState(0); const increment1 = useCallback(()=> setCount1(count1 + 1),[count1]); // const increment1 = ()=> setCount1(count1 + 1); ←これを使うとbutton2をクリックした場合でもincrement1の方のコンポーネントが再描画される。 const [count2,setCount2]=useState(0); const increment2 = useCallback(()=> setCount2(count=>count+1),[]); return( <div> <p>{count1}</p> <Button id={'Increment1'} onClick={increment1}/><p>{count2}</p> <Button id={'Increment2'} onClick={increment2}/> </div> ); } export default App; |
useMemoとuseCallbackの使い分け
useMemoは単純に計算結果を保持するために使います。(Vue.jsのcomputedに近いイメージです。)
useCallbackは、React.memoを使ったとしても、即時関数をpropsとして渡すと再レンダリングが行われてしまいます。(React.memoを使った際のアンチパターンのようです。)即時関数をuseCallbackにラップすることで、別コンポーネントに渡す際に再レンダリングが起こらないようにする用途で使えます。
むしろ、React.memoと併用して使う以外の用途はない。memo化されてないコンポーネントに対してuseCallbackでメモ化したpropsを渡しても結局再レンダリングされてしまうため。
また、カスタムフックを使った場合は基本的にはpropsで渡されることも想定して汎用性を持たせるためにuseCallbackを使うことが推奨されます。
この記事へのコメントはありません。