ここではReack hookのひとつ、useCallbackについて紹介していきたいと思います。
このhookはReactにおけるパフォーマンス最適化戦略と関わってくるところが大きいので、まずはそこから見ていきましょう。
Contents
Reactのパフォーマンス最適化
Reactでは、パフォーマンス最適化のためには「不要な再計算やコンポーネントの再レンダリングを抑える」ことが基本的な戦略です。
その実現のために使われるのが、React.memoやuseCallback、useMemoです。今回はuseMemo以外の2つを取り上げます。
React.memo
React.memoとは、コンポーネント(のレンダリング結果)を「メモ化(計算結果を保持し、再利用できるように)」するものです。いわゆる「短期記憶」、キャッシュですね。
このReact.memoを用いてレンダリングコストが高いコンポーネントや、頻繁に再レンダリングされるコンポーネント内のサブコンポーネントのリレンダーをスキップすることで、パフォーマンス向上が見込めます。
React.memoの例
import React from 'react';
export default function App() {
const Hello = React.memo(({ name }) => {
return <h1>Hello, React</h1>;
});
return (
<div className="App">
<Hello />
</div>
);
}
React.memoはPropsの等価性をチェックし、再レンダリングするか判断をします。つまり、新たに渡されたPropsと保持していたPropsとを比較し、等しい値であればメモ化したコンポーネントを再利用する、という処理を内部で行なっています。上のサンプルコードで言うと、props.nameが更新されない限りはコンポーネントの再レンダリングは実施されません。
しかし、このメモ化の仕組みで困ったことが起こります。コールバック関数をPropsとして受け取った場合、等価性を満たせず常に再レンダリングが走ってしまうのです。関数の内容が同じでも、その参照が異なるからです。
// コールバック関数を受け取るせいで常に再レンダリングが走ってしまうコード
import React, { useState } from "react";
const Child = React.memo(({ handleClick }) => {
console.log("I am Child.");
return <button onClick={handleClick}>Child</button>;
});
export default function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
console.log("clicked");
};
return (
<>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child handleClick={handleClick} />
</>
);
}
この問題を解消するためのhookが、useCallbackです。
useCallback
useCallbackは、先に見たReact.memoにおける問題を避けるため、メモ化したコールバック関数を返すhookです。React.memoでメモ化したコンポーネントに対し、useCallbackでメモ化したコールバック関数をPropsとして渡すことで、コンポーネントが必要以上にレンダリングされることを防ぎます。関数の参照を等価に保つhook、ということですね。
useCallbackの例と注意点
以下は、先に挙げた「コールバック関数を受け取るせいで常に再レンダリングが走ってしまうコード」をuseCallbackで書き換えた例です。
import "./styles.css";
import React, { useCallback, useState } from "react";
const Child = React.memo(({ handleClick }) => {
console.log("I am Child.");
return <button onClick={handleClick}>Child</button>;
});
export default function App() {
const [count, setCount] = useState(0);
// useCallbackでメモ化して再レンダリング抑制
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return (
<>
<p>Counter: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<Child handleClick={handleClick} />
</>
);
}
注意点として、コールバック関数をメモ化するuseCallbackはあくまでもコンポーネントをメモ化するReact.memoと併用するものなので、以下のように使っても本来期待する再レンダリング抑制の効果は得られないことが挙げられます。
- React.memoでメモ化していないコンポーネントに対して、useCallbackでメモ化した関数を渡す
- useCallbackでメモ化した関数を、その生成元のコンポーネント自身で利用する
返信がありません