ReactのuseMemo/useCallbackについて理解を深める
useMemo/useCallback
- メモ化された値/関数を返す関数
- 値 -> useMemo
- 関数 -> useCallback
- useCallback(fn, deps) は useMemo(() => fn, deps) 等価
- 第2引数には関数が依存する値の配列を渡す
- 依存配列の要素のいずれかが変化した場合にのみ再計算する
- eslint-plugin-react-hooks パッケージの exhaustive-deps ルールを有効にするべき
- 第2引数にオブジェクト渡したらどうなる?
- Object.is で比較するらしい
useMemo
useCallback
areHookInputsEqual
shared/objectIs.js
そもそも関数の呼び出しのオーバーヘッドを鑑みて、useCallbackはむやみに使うなという風潮?があったらしい。useCallbackが本当に効果のあるタイミングは、Componentにイベントハンドラーを渡すとき。(propsの値が変化したかどうかで再レンダーの有無を判断するため) DOMに渡すイベントハンドラーには効果がないはず。
この文は説得力がある
もしも「useToggleの返り値が毎回変わっていてSuperHeavyButtonが再レンダリングされてしまい困るからuseToggleにuseCallbackを追加した」というようなことが起こった場合は、それはuseToggleを使う側の都合を鑑みてuseToggleを仕様変更したということになります。 つまり、useToggleをコンポーネントロジックから分離して再利用可能にしたつもりが、結局使う側に振り回されてしまい再利用可能になっていなかったということです。
Recat.memo と component
nus3さんの記事がとても良かった。自分のために写経する。
以下のようなコンポーネントを用意する。
import { useState, useEffect, useMemo, useCallback } from "react";
import "./styles.css";
const Child = (props) => {
useEffect(() => {
console.log("Childがレンダリングされたよ");
});
return <div>countForChild: {props.value}</div>;
};
export default function App() {
const [countForParent, setCountForParent] = useState(0);
const [countForChild, setCountForChild] = useState(0);
useEffect(() => {
console.log("Parentがレンダリングされたよ");
});
return (
<div className="App">
<div>
<button onClick={() => setCountForParent(0)}>
set 0 to countForParent
</button>
<button onClick={() => setCountForParent(1)}>
set 1 to countForParent
</button>
</div>
<Child value={countForChild} />
<div>countForParent: {countForParent}</div>
</div>
);
}
このとき、「set 0 to countForParent」と「set 1 to countForParent」のボタンどちらをクリックしても、Childコンポーネントまで再レンダーされる。これは、ParentコンポーネントでcountForParent
が利用されているため、Parentコンポーネント自身が再レンダーされ、その子要素であるChildコンポーネントまで再レンダーするため。一方で、Childコンポーネントは、countForParent
には関与してないため本来は再レンダーされてほしくないはずである。
こういったケースでChildコンポーネントを再レンダーしないようにするためにはメモ化を使う。
// `React.memo` を使うやり方
const ChildMemo = React.memo(({ value }) => <Child value={value} />)
return (
<div>
<ChildMemo count={childCount} />
</div>
)
// useMemoを使うやり方
const childMemo = useMemo(() => <Child count={childCount} />, [childCount])
return (
<div>
{childMemo}
</div>
)