再レンダリング
再レンダリングの条件
再レンダリング(関数コンポーネントの先頭から処理が実行)が起きるパターン
- Stateが更新されたコンポーネント
- Propsが変更されたコンポーネント
- 再レンダリングされたコンポーネント配下のコンポーネント
注意したいのは3の場合で、例えばApp.jsのStateを更新すると全てのコンポーネントが再レンダリングされてしまう。
親コンポーネントが再レンダリングされたら、子コンポーネントは自身の表示は何も変わらないのに無駄な再レンダリングが発生してしまう。
そうならないように、コードで再レンダリングの最適化を行う必要がある。
コンポーネントのメモ化(memo関数)
再レンダリング時のコンポーンネント、変数、関数などを制御するにはメモ化を行う必要がある。
メモ化とは前回の処理結果をキャッシュすることである。
コンポーネントのメモ化の実装は簡単で、コンポーネント関数全体をmemo関数で囲むだけである。
import { memo } from 'react';
const 変数 = memo(コンポーネント関数);
たったこれだけでPropsに変更がない限り再レンダリングされないようになるので、レンダリングコストの高いコンポーネントは積極的にメモ化するべきである。
逆に、レンダリングコストが高くないコンポーネントはメモ化しても意味がないので注意が必要である。
import { memo } from 'react';
const Ko = memo((props)=>{
return (
<h1>子{props.hoge}</h1>
);
});
export default Ko;
関数のメモ化(useCallback関数)
関数をPropsとして子コンポーネントに渡す場合、親コンポーネントで再レンダリングが発生すると、子コンポーネントはメモ化されていても再レンダリングが発生してしまう。
これは、親コンポーネントで再レンダリングが発生した際に関数が再生成され、子コンポーネントはPropsが変更されたと判定するからである。
以下の例の場合、OyaのボタンをクリックするとOyaが再レンダリングされresetNum関数が再生成される。
そのため、KoはPropsが変化したと判定して再レンダリングされてしまう。
import { useState } from 'react';
import Ko from './Ko';
function Oya() {
const [num, setNum] = useState(0);
const resetNum = () => {
setNum(0)
}
return (
<>
<button onClick={() => setNum(num+1)}>increment</button>
<Ko resetNum={resetNum}/>
</>
);
}
export default Oya;
import { memo } from 'react';
const Ko = memo(({ resetNum })=>{
return (
<>
<button onClick={resetNum}>resetNum</button>
</>
)
})
export default Ko;
これを回避するためにはuseCallback関数を使って関数のメモ化を行う必要がある。
import { useCallback } from 'react';
const 変数 = useCallback(関数, 依存配列);
useCallbackはuseEffectと同様に、第1引数に関数、第2引数に依存配列を受け取る。
関数内でprops, stateなどリアクティブな値や関数を参照している場合、第2引数の依存配列に含める必要がある。
第2引数に含めないと関数定義の時点の値が利用され、リアクティブに変更された値が反映されない。
import { useState, useCallback } from 'react';
import Ko from './Ko';
function Oya() {
const [num, setNum] = useState(0);
const resetNum = useCallback(() => {
setNum(0)
}, [])
return (
<>
<button onClick={() => setNum(num+1)}>increment</button>
<Ko resetNum={resetNum}/>
</>
);
}
export default Oya;
どこまでメモ化するか?
- 小規模アプリでは意識しなくても良い
- データ量の多いコンポーネントはメモ化するのが望ましい
- Propsで渡す関数はメモ化しておくのが望ましい
変数(関数の結果)のメモ化(useMemo関数)
変数に代入する値の生成に膨大な数のループがある場合などは、useMemo関数を使って変数をメモ化することで負荷を下げることができる。
第2引数を空配列にすることで初回のみ変数に代入される。
import { useMemo } from 'react';
const 変数 = useMemo(値を返す関数, []);
ただし、メモ化にも処理コストはかかるため、useMemoを使うことで逆に処理速度が遅くなってしまう可能性もある。
参考サイト
https://off.tokyo/blog/react-usememo-when-to-use/