どうも、@ちーずです。
アドベントカレンダー5日目、本日のテーマはReactにおけるメモ化に関してです!!
そもそもメモ化とは?
プログラムの高速化のための最適化技法の一種であり、サブルーチン呼び出しの結果を後で再利用するために保持し、そのサブルーチン(関数)の呼び出し毎の再計算を防ぐ手法である。
メモ化された関数は、以前の呼び出しの際の結果をそのときの引数と共に記憶しておき、後で同じ引数で呼び出されたとき、計算せずにその格納されている結果を返す。
参考: メモ化 - Wikipedia
つまり、計算結果をキャッシュし、それを再利用することをメモ化と言います。
なぜメモ化が必要?
Reactでは下記のようなタイミングでComponentが再レンダリングされます。
- state / props が更新された時
- 親コンポーネントが再レンダリングされた時
Componentが再レンダリングすると、
そのComponent内で定義している関数や変数も新しく生成されてしまい、
値が変わっていなかったとしても再計算されてしまいます。
Reactでは油断しているとそのような予期していない再計算が増えてしまい、
サイトのパフォーマンスが影響する可能性があるため、メモ化の重要度が高いです。
メモ化する時の注意点
軽い処理に対してメモ化すると、キャッシュを参照するオーバーヘッドが発生します。
また、メモ化の必要性はアプリケーションによっても異なります。
そのため、何でもかんでもメモ化するのではなく、実行してみてパフォーマンスがちゃんと改善したか確認しながらメモ化するとよりパフォーマンスの向上に繋がると思います。
(ちなみに自分はなんでもかんでもメモ化しちゃっていました...反省)
Componentをメモ化 - React.memo
React.memo
は、Componentをメモ化するHOC(higher-order component) です。
Propsの値に変更がなかった際の再レンダリングを抑制することができます。
HOCとは、あるコンポーネントを受け取って新規のコンポーネントを返すような関数のことです。
書き方
// 第一引数: 関数Component
export const heavyComponent = React.memo(() => (
// とても思い処理
<p>子Componentです</p>
));
どんな時に使う?
レンダリングコストが高いコンポーネント
(そもそも、値のメモ化でどうにかなるケースの方が多そうですが...)
Component自体が重い処理をもっている場合は、メモ化することでレンダリングを避けることができます。
頻繁に再レンダリングされてしまう可能性があるコンポーネント
親Componentの変更が頻繁に発生する場合、子コンポーネントがその度に不要な再レンダリングをしてしまうため
React.memo
でメモ化することおすすめします。
親Componentの処理に依存してComponentがのレンダリングが頻繁に起きてしまう場合、
そもそものComponent設計が正しくない可能性もあります。
そのような場合は、脳死メモ化するのではなく一度設計を見直した上でメモ化することをおすすめします。
値をメモ化 - useMemo
useMemo
は、実行結果や値をメモ化することができるフックです。
書き方
// 第一引数: コールバック関数
// 第二引数: 依存配列 - この値が変更された時のみ再計算
useMemo(() => function(), [deps])
どんな時に使う?
非常にコストのかかる計算を行う場合
非常にコストのかかるの定義は難しいですが、
下記記事をよんでみると、100回くらいの配列のループでuseMemo
を使うのは意味がないかなぁ。。。と思いました。
▼ 参考: メモ化していない時と
- 500回以上のループ処理並の複雑さの処理
- 何度も再レンダーされる可能性がある
時に使えるかなと思いました!
関数をメモ化 - useCallback
useCallback
はメモ化されたコールバック関数を返すhooksです。
書き方
// 第一引数: コールバック関数
// 第二引数: 依存配列
const func = useCallback(() => {
// callback関数
}, [deps]);
どんな時に使う?
関数を返すカスタムhooks
カスタムフックは、再利用性を高いhooksを定義するためのものであり、
Component側で呼ばれる際にその関数が再生成されることを利用者側が考えなくても良いように設計すべきです。
そのため、カスタムフックが関数を返すなら、基本的にメモ化することをお勧めします。
筆者は下記記事にものすごく感銘を受けたので、より詳しく知りたい人はぜひ読んでください!!
▼ 参考
メモ化されたComponentに関数を渡す場合
React.memo
の復習になりますが、propsの値が同一である場合は再レンダリングは走りません。
しかし、そのまま関数をメモ化された子Componentに渡してしまうと
親が再レンダリングされたタイミングで関数が再生成されてしまうため、同一の値と判定されないため再レンダリングが走ります。
そのため、関数の同一性を保つためにuseCallback
で囲いましょう!
以上 「Reactにおけるメモ化に関して」 でした!!
いつか、メモ化パフォーマンス改善実験をやってみたいな〜と思います!!