Reactアプリが大きくなってくると気になってくるのが「再レンダリングの無駄」。useCallback
とuseMemo
は、処理の無駄を減らすための最適化フック。ただし、やみくもに使うと逆に読みづらくなるので、使いどころがわかってると強い武器になる。
Reactは「状態が変わると、そのコンポーネントと子孫が再レンダリングされる」仕組み。
このときpropsとして渡す関数や値が毎回新しく生成されると、子コンポーネントも無駄に再描画されることがある。
tsx
const handleDelete = useCallback((id: number) => {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
}, []);
handleDelete
は毎回再生成される(=参照が毎回変わる)useCallback
でラップすると、依存が変わらない限り同じ参照を再利用できるReact.memo
と一緒に使うと特に効果的tsx
<TodoItem todo={todo} onDelete={handleDelete} />
memo()
でメモ化されているなら、onDelete
の参照が変わらなければ再レンダリングされないuseCallback
がないと、親が再レンダリングされるたびに子も巻き込まれるtsx
const todoCount = useMemo(() => {
console.log("Calculating todo count...");
return todos.length;
}, [todos]);
todos.length
が再計算されるuseMemo
を使えば、todos
が変わらない限り前回の計算結果を再利用できるconsole.log
で確認すると、無駄な計算が減っているのがわかるフック | 対象 | 目的 |
---|---|---|
useCallback | 関数 | 同じ関数を再利用 |
useMemo | 計算結果 | 計算結果を再利用 |
tsx
const TodoItem = memo(({ todo, onDelete }) => {
console.log(`rendered: ${todo.text}`);
return <div>{todo.text}</div>;
});
memo
で子コンポーネントをpropsが変わらない限り再レンダリングしないようにできるuseCallback
やuseMemo
でpropsの参照を変えないようにすれば、子の再レンダリングを防げるconsole.log("rendered")
や"Calculating..."
を仕込んで様子を見るReact DevTools
で再レンダリングの状況を確認する👇 サンプルアプリ:useCallback/useMemo 学習アプリ
useCallback
:関数をメモ化して子コンポーネントへの無駄な再レンダリングを防ぐuseMemo
:重たい処理の結果をメモ化して毎回再計算を防ぐReact.memo
と組み合わせると効果が見える