導入
state が変わるとコンポーネントは再描画されます。ふつうは十分速いのですが、重い計算や大量の子コンポーネントがあるアプリでは、「その state とは関係ない場所まで毎回計算し直す・再描画する」ことが目に見えて遅さにつながることがあります。useMemo・useCallback・React.memo は、そうした無駄を後から削るための道具です(最初から全部に付ける必要はありません)。
説明
// わざと時間のかかる計算にしている(本来はもっと重い処理を想定)
function slowSquare(n) {
let result = 0;
for (let i = 0; i < 3000000; i++) {
result = n * n;
}
return result;
}
// React.memo … 渡された props が変わらない限り再描画をスキップする
const ChildButton = React.memo(function ChildButton({ onClick }) {
console.log("ChildButton が再描画されました");
return <button onClick={onClick}>押す(子コンポーネント)</button>;
});
function App() {
const [number, setNumber] = React.useState(2);
const [other, setOther] = React.useState(0);
// useMemo … number が変わらない限り slowSquare を再実行しない
const squared = React.useMemo(() => slowSquare(number), [number]);
// useCallback … 中身が変わらない限り「同じ関数」を使い回す
const handleClick = React.useCallback(() => {
console.log("クリックされました");
}, []);
return (
<div style={{ fontFamily: "system-ui", maxWidth: 320 }}>
<p>{number} の2乗 = {squared}</p>
<button onClick={() => setNumber((n) => n + 1)}>numberを増やす</button>
<p>他の状態: {other}</p>
<button onClick={() => setOther((o) => o + 1)}>otherだけ増やす</button>
<ChildButton onClick={handleClick} />
</div>
);
}
React.useMemo(() => 計算, [依存])… 依存配列の値が変わらない限り、前回の計算結果を使い回します。「otherだけ増やす」を押してもnumberは変わらないのでslowSquareは再実行されません。React.memo(Component)… 渡された props が(前回と)変わっていなければ、そのコンポーネントの再描画を丸ごとスキップします。- ただし関数をそのままインラインで渡す(
onClick={() => ...})と、描画のたびに毎回新しい関数が作られるため、React.memoは「props が変わった」と判定してしまいます。React.useCallback(関数, [依存])で関数そのものを使い回すことで、React.memoが効くようになります。 - 最適化は「遅いと感じてから」で十分です。すべての値・関数に
useMemo/useCallbackを付けるのはやり過ぎで、かえって読みにくくなります。
やってみよう
React.useCallback をやめて handleClick をただの関数(const handleClick = () => { ... })に書き換え、「otherだけ増やす」を押すたびにコンソールへ ChildButton が再描画されました が出るようになることを確認しましょう。useCallback があるときは、そのログが最初の1回しか出ないはずです。