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と組み合わせると効果が見える