はじめに
React のフックは大きく分けると 2つの分類 で考えるとわかりやすいです。
- 1つ目は 状態や値を保持する系 です。ここには useState と useRef が含まれます。
useState は UI に反映させる値を保持するために使い、値が変わるとコンポーネントが再レンダーされます。
useRef は DOM 要素や再レンダーさせたくない値を保持するために使い、値が変わっても再レンダーはされません。 - 2つ目は 計算や関数をキャッシュする系 です。ここには useMemo と useCallback が含まれます。
useMemo は計算結果(値)をキャッシュして、依存する値が変わらない限り再計算を避けます。
useCallback は関数そのものをキャッシュして、依存する値が変わらない限り同じ関数参照を再利用します。
useCallback と useMemo は 「動作させるため」ではなく「パフォーマンスを最適化するため」 のフックです。
もしコードがそれなしで動作しない場合は、背後にある問題を見つけてまずそれを修正してください。
それぞれの特徴
useMemo
計算結果(値)をキャッシュする
React がレンダリングするときに「渡された関数をすぐ実行」して、その戻り値を保存します。
const doubled = useMemo(() => number * 2, [number]);
// number が変わるたびに number * 2 を計算してキャッシュ
useCallback
関数そのものをキャッシュする
React は「渡された関数を実行せずに」その関数をそのまま保存します。
const handleSubmit = useCallback((e) => {
e.preventDefault();
alert(text);
}, [text]);
<form onSubmit={handleSubmit}>...</form>
//text が変わらない限り同じ関数を再利用。
使い分け
useMemo
-
重い計算
フィルタリング、ソート、集計、地図データの変換など -
オブジェクトや配列を安定させたいとき
子コンポーネントに渡す props が [] や {} だと毎回新しい参照になるので、useMemo で固定する
useCallback
-
子コンポーネントへの関数 props
関数が毎回新しく作られると、子が「props が変わった」と判断して無駄に再レンダリングされる -
useEffect の依存に関数を入れるとき
関数が毎回変わると無限ループになるので固定したい
使いまくれば良いってもんじゃない
公式の記事にもあるとおり、useMemo・useCallback は パフォーマンス最適化が必要な場合にのみ使うべき です。
不要な場面で使うとコードが複雑になり、可読性やデバッグ性が下がります。
ただしオーバーヘッドは小さいため、プロジェクトによっては「常に使う」方針を取るチームもあります。
結局は チームでルールを決めて統一すること が大事です。
個人的には「まずはシンプルに書き、必要なときに計測して導入する」方が良いと考えていますが、カスタムフックなどでは積極的に使うメリットもあります。