以下のUdemyの講座を受講したので、学んだことをまとめていく。
レンダリングの最適化
再レンダリングされる条件と再レンダリングによるパフォーマンス低下が起こる例、及びそれを解消するための方法について学んだ。
再レンダリングされる条件
- stateが更新されたコンポーネントは再レンダリングされる
- propsが変更されたコンポーネントは再レンダリングされる
- 再レンダリングされたコンポーネント配下の子要素は再レンダリングされる
//App.jsx
import './App.css';
import { ChildArea } from './ChildArea';
export const App = () => {
const [text, setText] = useState('');
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickCountUp = () => {
setConut(count + 1);
};
const onClickOpen = () => {
setOpen(!open);
};
return (
<>
<input value={text} onChange={onChangeText} />
<br />
<br />
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open} />
</>
);
};
//ChildArea.jsx
export const ChildArea = (props) => {
const { open } = props;
console.log('ChildAreaがレンダリングされた!');
const data = [...Array(2000).keys()];
data.forEach(() => {
console.log('...');
});
return (
<>
{open ? (
<div style={style}>
<p>子こんぽーねんと</p>
</div>
) : null}
</>
);
};
上記の例では、「表示」ボタンを押すとChildAreaに渡されているopenというPropsが更新されるので、ChildAreaコンポーネントは再レンダリングされる。
テキストボックスが更新された場合、textステートが更新されるので、Appコンポーネントが再レンダリングされる。
その際、ChildAreaコンポーネントはAppコンポーネントの子要素にあたるため、ChildAreaコンポーネントも再レンダリングされてしまう。
memo関数
memoという関数を使うと、Propsが変更された時のみ再レンダリングするようにできる。
コンポーネントをmemo関数で囲むように使用する。
//ChildArea.jsx
export const ChildArea = memo((props) => {
const { open } = props;
console.log('ChildAreaがレンダリングされた!');
const data = [...Array(2000).keys()];
data.forEach(() => {
console.log('...');
});
return (
<>
{open ? (
<div style={style}>
<p>子こんぽーねんと</p>
</div>
) : null}
</>
);
});
この場合、表示ボタンを押すとPropsが更新されるので再レンダリングされる。
テキストボックスが更新された場合は親コンポーネントの再レンダリングが起きるが、memo関数により、ChildAreaコンポーネントは再レンダリングされない。
useCallback関数
下記の例では、PropsにonClickCloseという関数を渡しているが、Propsに毎回新しい関数を生成して渡している扱いになるため、ChildAreaコンポーネントの再レンダリングが発生する。
//App.jsx
import { useState } from 'react';
import './App.css';
import { ChildArea } from './ChildArea';
export const App = () => {
const [text, setText] = useState('');
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickClose = () => setOpen(false);
const onClickOpen = () => {
setOpen(!open);
};
return (
<>
<input value={text} onChange={onChangeText} />
<br />
<br />
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open} onClickClose={onClickClose} />
</>
);
};
//ChildArea.jsx
import { memo } from 'react';
const style = {
width: '100%',
height: '200px',
backgroundColor: 'khaki',
};
export const ChildArea = memo((props) => {
const { open, onClickClose } = props;
console.log('ChildAreaがレンダリングされた!');
const data = [...Array(2000).keys()];
data.forEach(() => {
console.log('...');
});
return (
<>
{open ? (
<div style={style}>
<p>子こんぽーねんと</p>
<button onClick={onClickClose}>閉じる</button>
</div>
) : null}
</>
);
});
useCallback関数を使用すると、条件を満たした場合のみ再生成するようにできる。
これにより、関数の再生成による再レンダリングを抑制できる。
const onClickClose = useCallback(() => setOpen(false), []);
使い方はuseEffectと同様、第1引数に関数の処理を記述する。
useEffectの第2引数に空配列を指定すると、マウント時(ページが最初に読み込まれる時)に1度だけ生成される。
第2引数の配列要素に変数を指定すると、指定した変数が更新された場合に第1引数に記述した関数が再生成される。