React プロジェクトをやったことがある人は、こんな状況に遭遇したことがあるでしょう:開発中はページがスムーズに動くのに、データ量が増えるとカクカクになってしまう。ボタンをクリックするのに 3 秒待たされる、うちの猫が起きるより遅い。
すぐにバックエンドのせいにしないでください。今日は React でよく見落とされがちだけど、とても役に立つものについて話します —— useMemo
。
この名前は「記憶がある」ように聞こえますが、実際にはコンポーネントの省エネモードです。
useMemo とは#
あなたに同僚がいると想像してください。複雑な質問をするたびに、彼は計算に時間がかかります。重要なのは、毎回質問するたびに彼は再計算を行うことです。データが全く変わっていなくても。
useMemo
はこの同僚に記憶を持たせたようなものです:前回計算した結果を覚えているので、直接使ってもいいですか?
本質的には計算結果をキャッシュすることで、依存関係が変わらなければ古い結果をそのまま返し、再計算を避けます。
構文はこんな感じです:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
a
またはb
が変わったときだけ再計算され、それ以外は前回の結果をそのまま返します。
いつ使うべきか#
1. 計算が重いとき#
例えば、5000
件の注文データを処理してソート、フィルタリング、集計する場合。このような操作は毎回ファンが回り始めるほど重く、コンポーネントは一日に何十回もレンダリングされます。
const processedData = useMemo(() => {
return heavyProcess(orders); // 時間がかかる操作
}, [orders]); // ordersが変わったときだけ再計算
これにより、毎回のレンダリングで計算するのではなく、1 回だけ計算することになります。
2. 子コンポーネントにオブジェクトや配列を渡すとき#
子コンポーネントがReact.memo
を使っているのに、常に再レンダリングされる?それは渡される prop が毎回新しい参照になっている可能性が高いです。
例えば、こんな感じ:
// 毎回レンダリングで新しい配列を作成、内容が同じでも
<MyComponent filters={[{ type: "active" }]} />
解決方法:
const stableFilters = useMemo(() => [{ type: "active" }], []);
<MyComponent filters={stableFilters} />;
これでオブジェクトのアドレスが変わらず、React.memo
が正常に機能します。
3. 他の Hook の依存項目として使うとき#
const config = { userId, theme };
useEffect(() => {
fetchUserData(config);
}, [config]); // 問題が発生します!
毎回のレンダリングでconfig
は新しいオブジェクトになり、useEffect
が繰り返しトリガーされます。
解決策:
const config = useMemo(() => ({ userId, theme }), [userId, theme]);
useEffect(() => {
fetchUserData(config);
}, [config]);
これでuserId
またはtheme
が変わったときだけconfig
が更新されます。
useMemo vs useCallback#
この 2 つは混同しやすいです:
Hook | キャッシュするもの | 使用シーン |
---|---|---|
useMemo | 1 つの値 | 複雑な計算、オブジェクト / 配列、依存項目として |
useCallback | 1 つの関数そのもの | 関数の変化による子コンポーネントの再レンダリングを防ぐ |
// useMemo: "計算結果"をキャッシュ
const total = useMemo(() => items.reduce(sum), [items]);
// useCallback: "関数そのもの"をキャッシュ
const handleClick = useCallback(() => doSomething(id), [id]);
よくある落とし穴#
依存項目を間違える#
useMemo(() => expensive(), [{}]); // 毎回新しいオブジェクト!
useMemo(() => expensive(), [[]]); // 毎回新しい配列!
依存項目が安定した値であることを確認してください。
useMemo の乱用#
すべての計算をキャッシュする必要はありません。単純なa + b
の場合、useMemo
を使うと逆に無駄になります。
React の公式も言っています:早すぎる最適化は悪の根源です。まずパフォーマンスのボトルネックを見つけてからuseMemo
を使いましょう。
まとめ#
いつ使うべきか:
- 計算が重いとき
memo
子コンポーネントに渡すオブジェクト / 配列- 他の
Hook
の依存項目として
覚えておいてください:それはスイスアーミーナイフであって、バンドエイドではありません。
ページがカクカクしているからといってすぐにフレームワークを変えないで、まずuseMemo
に給料を支払うべきかどうかを考えてみてください。