この記事の概要
React Compiler によって自動的にメモ化が行われるそうです。
それ自体は非常にありがたいのですが、手動でメモ化したときと、React Compiler に任せたときとで、パフォーマンスに違いはあるのだろうか?と気になって調べてみました。
実験に使ったコード
import { useState, useMemo, useCallback } from "react";
// Function to perform heavy calculation
const heavyCalculation = (num) => {
console.time("Calculation time");
let result = 0;
for (let i = 0; i < 500000000; i++) {
result += num;
}
console.timeEnd("Calculation time");
return result;
};
// Component without using useMemo and useCallback
const WithoutMemoization = ({ num }) => {
const [count, setCount] = useState(0);
const result = heavyCalculation(num);
const handleClick = () => {
setCount(count + 1);
};
console.time("Rendering time (Without Memoization)");
const output = (
<div>
<p>Result: {result}</p>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment Count</button>
</div>
);
console.timeEnd("Rendering time (Without Memoization)");
return output;
};
// Component using only useMemo
const WithUseMemoOnly = ({ num }) => {
const [count, setCount] = useState(0);
const result = useMemo(() => heavyCalculation(num), [num]);
const handleClick = () => {
console.time("handleClick execution time (useMemo only)");
setCount(count + 1);
console.timeEnd("handleClick execution time (useMemo only)");
};
console.time("Rendering time (useMemo only)");
const output = (
<div>
<p>Result: {result}</p>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment Count</button>
</div>
);
console.timeEnd("Rendering time (useMemo only)");
return output;
};
// Component using only useCallback
const WithUseCallbackOnly = ({ num }) => {
const [count, setCount] = useState(0);
const result = heavyCalculation(num);
const handleClick = useCallback(() => {
console.time("handleClick execution time (useCallback only)");
setCount((prevCount) => prevCount + 1);
console.timeEnd("handleClick execution time (useCallback only)");
}, []);
console.time("Rendering time (useCallback only)");
const output = (
<div>
<p>Result: {result}</p>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment Count</button>
</div>
);
console.timeEnd("Rendering time (useCallback only)");
return output;
};
// Component using both useMemo and useCallback
const WithMemoization = ({ num }) => {
const [count, setCount] = useState(0);
const result = useMemo(() => heavyCalculation(num), [num]);
const handleClick = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
console.time("Rendering time (With Memoization)");
const output = (
<div>
<p>Result: {result}</p>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment Count</button>
</div>
);
console.timeEnd("Rendering time (With Memoization)");
return output;
};
const App = () => {
return (
<div>
<h1>Test useMemo / useCallback</h1>
<h2>Without both useMemo and useCallback</h2>
<WithoutMemoization num={10} />
<h2>With useMemo only</h2>
<WithUseMemoOnly num={10} />
<h2>With useCallback only</h2>
<WithUseCallbackOnly num={10} />
<h2>With both useMemo and useCallback</h2>
<WithMemoization num={10} />
</div>
);
};
export default App;
-
useMemo
もuseCallback
もなし -
useMemo
だけ -
useCallback
だけ -
useMemo
もuseCallback
もあり
これらの条件で、React 18 と React 19 との違いを見てみます。
Increment Count
ボタンを 3 回押した分のコンソールの内容を載せています。
React 18
useMemo
もuseCallback
もなし
すべてのパターンにおいて、Strict Mode なので 2 回レンダリングされています。
1回目
handleClick execution time: 2.469970703125 ms
Calculation time: 510.0908203125 ms
Rendering time: 0.02685546875 ms
Calculation time: 474.7578125 ms
Rendering time: 0.028076171875 ms
2回目
handleClick execution time: 0.097900390625 ms
Calculation time: 512.637939453125 ms
Rendering time: 0.0390625 ms
Calculation time: 476.7060546875 ms
Rendering time: 0.030029296875 ms
3回目
handleClick execution time: 0.071044921875 ms
Calculation time: 491.699951171875 ms
Rendering time: 0.0419921875 ms
Calculation time: 475.31787109375 ms
Rendering time: 0.02685546875 ms
useMemo
だけ
1回目
handleClick execution time: 0.159912109375 ms
Rendering time: 0.037841796875 ms
Rendering time: 0.022216796875 ms
2回目
handleClick execution time: 0.13916015625 ms
Rendering time: 0.034912109375 ms
Rendering time: 0.01904296875 ms
3回目
handleClick execution time: 0.078125 ms
Rendering time: 0.014892578125 ms
Rendering time: 0.005859375 ms
useCallback
だけ
1回目
handleClick execution time: 0.108154296875 ms
Calculation time: 492.697998046875 ms
Rendering time: 0.02490234375 ms
Calculation time: 474.359130859375 ms
Rendering time: 0.02587890625 ms
2回目
handleClick execution time: 0.1181640625 ms
Calculation time: 510.855224609375 ms
Rendering time: 0.02490234375 ms
Calculation time: 474.216064453125 ms
Rendering time: 0.02880859375 ms
3回目
handleClick execution time: 0.07421875 ms
Calculation time: 497.56396484375 ms
Rendering time: 0.02587890625 ms
Calculation time: 474.2119140625 ms
Rendering time: 0.022216796875 ms
useMemo
もuseCallback
もあり
1回目
handleClick execution time: 0.072021484375 ms
Rendering time: 0.032958984375 ms
Rendering time: 0.01708984375 ms
2回目
handleClick execution time: 0.10302734375 ms
Rendering time: 0.0439453125 ms
Rendering time: 0.028076171875 ms
3回目
handleClick execution time: 0.072021484375 ms
Rendering time: 0.042236328125 ms
Rendering time: 0.01806640625 ms
React 19
アップグレードガイドにある通りに React 本体をアップグレードした後、React Compiler を適用しました。
npm install babel-plugin-react-compiler
今回は Vite を使っていたので、vite.config.js
に設定を追加します。
こちらもスタートガイドにある通りです。
vite.config.js
export default defineConfig(() => {
return {
plugins: [
react({
babel: {
plugins: [
["babel-plugin-react-compiler"],
],
},
}),
],
};
});
ReactCompilerConfig
は不要だったので指定していません。
useMemo
もuseCallback
もなし
1回目
handleClick execution time: 0.5 ms
Rendering time: 0.0400390625 ms
Rendering time: 0.027099609375 ms
2回目
handleClick execution time: 0.134033203125 ms
Rendering time: 0.0419921875 ms
Rendering time: 0.01708984375 ms
3回目
handleClick execution time: 0.098876953125 ms
Rendering time: 0.033935546875 ms
Rendering time: 0.031982421875 ms
useMemo
だけ
1回目
handleClick execution time: 0.114990234375 ms
Rendering time: 0.057861328125 ms
Rendering time: 0.02099609375 ms
2回目
handleClick execution time: 0.119140625 ms
Rendering time: 0.031005859375 ms
Rendering time: 0.01611328125 ms
3回目
handleClick execution time: 0.06689453125 ms
Rendering time: 0.029052734375 ms
Rendering time: 0.027099609375 ms
useCallback
だけ
1回目
handleClick execution time: 0.13818359375 ms
Rendering time: 0.030029296875 ms
Rendering time: 0.014892578125 ms
2回目
handleClick execution time: 0.115966796875 ms
Rendering time: 0.029052734375 ms
Rendering time: 0.01318359375 ms
3回目
handleClick execution time: 0.158935546875 ms
Rendering time: 0.030029296875 ms
Rendering time: 0.033935546875 ms
useMemo
もuseCallback
もあり
1回目
handleClick execution time: 0.115966796875 ms
Rendering time: 0.056884765625 ms
Rendering time: 0.01806640625 ms
2回目
handleClick execution time: 0.055908203125 ms
Rendering time: 0.033203125 ms
Rendering time: 0.031982421875 ms
3回目
handleClick execution time: 0.055908203125 ms
Rendering time: 0.024169921875 ms
Rendering time: 0.009033203125 ms
比較とまとめ
上記の結果の平均値を出し、表にしています。
単位はすべて ms です。
useMemo もuseCallback もなし |
React 18 | React 19 |
---|---|---|
handleClick execution time | 0.8796386719 | 0.2443033854 |
Calculation time | 490.2017415 | なし |
Rendering time | 0.03214518229 | 0.03202311198 |
合計 | 491.1135254 | 0.2763264974 |
useMemo だけ |
React 18 | React 19 |
---|---|---|
handleClick execution time | 0.1257324219 | 0.1003417969 |
Rendering time | 0.0224609375 | 0.03035481771 |
合計 | 0.1481933594 | 0.1306966146 |
useCallback だけ |
React 18 | React 19 |
---|---|---|
handleClick execution time | 0.1001790365 | 0.1376953125 |
Calculation time | 487.3173828 | なし |
Rendering time | 0.0254313151 | 0.02518717448 |
合計 | 487.4429932 | 0.162882487 |
useMemo もuseCallback もあり |
React 18 | React 19 |
---|---|---|
handleClick execution time | 0.08235677083 | 0.07592773438 |
Rendering time | 0.03039550781 | 0.02888997396 |
合計 | 0.1127522786 | 0.1048177083 |
- (今回の結果でいえば)すべてを React Compiler に任せるより、手動でメモ化した方がわずかにパフォーマンスが良い
- 半端にメモ化してある箇所の自動メモ化がスキップされるようなことはなく、ちゃんと最適化されている
上記をもとに、今後のアップグレードを考えるのであれば以下でしょうか。
- 手動でメモ化してある箇所を、わざわざ外す必要はない
- メモ化が半端 or 間違っている箇所をわざわざ修正する必要はない
- (あるのか分からないが)ほんの少しでもパフォーマンスを良くしたい箇所があるなら手動でメモ化しておく
内部実装を読んで書いているわけではないので間違いや「場合による」箇所があるかもしれませんが、どなたかのお役に立てれば幸いです。