再レンダリング最適化について勉強した時のメモです。
再レンダリングが起きる条件
- stateが更新されたとき。
- propsが変更されたとき
- 親コンポーネントが再レンダリングされた時のコンポーネント配下の子要素。
以下の場合Bが再レンダリングされるとCも再レンダリングされる。
Stateが更新された時
import { useState } from "react";
export const App = () => {
console.log("Appレンダリング")
const [text, setText] = useState("");
const onChangeText = (e) => setText(e.target.value);
return (
<div className="App">
<input value={text} onChange={onChangeText} />
</div>
);
};
この場合inputのテキストボックスに値を入れるたびにconsoleが走るのでstateが変わるたびに再レンダリングが発生している。
propsが更新された時
import { useState } from "react";
import { ChildArea } from "./components/ChildArea";
export const App = () => {
const [text, setText] = useState("");
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickOpen = () => setOpen(!open);
return (
<div className="App">
<input value={text} onChange={onChangeText} />
<br />
<br />
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open} />
</div>
);
};
export const ChildArea = (props) => {
const { open } = props;
console.log("ChildAreaレンダリング");
return (
<>
{open ? (
<div>
<p>子コンポーネント</p>
</div>
) : null}
</>
);
};
ChildArea
にopen
というpropsを渡しているのでボタンを押すたびに再レンダリングが起きている。
親コンポーネントが再レンダリングされた時
import { useState } from "react";
import { ChildArea } from "./components/ChildArea";
export const App = () => {
const [text, setText] = useState("");
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickOpen = () => setOpen(!open);
return (
<div className="App">
<input value={text} onChange={onChangeText} />
<br />
<br />
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open} />
</div>
);
};
export const ChildArea = (props) => {
const { open } = props;
console.log("ChildAreaレンダリング");
return (
<>
{open ? (
<div>
<p>子コンポーネント</p>
</div>
) : null}
</>
);
};
この場合ChildArea
のpropsであるopen
には触れていないが、親コンポーネントに再レンダリングが起きているためChildArea
にも再レンダリングが起きている。
コンポーネント最適化
- memo
- useCallback
memo(コンポーネントをmemo化)
import { memo } from "react";
export const ChildArea = memo((props) => {
const { open } = props;
console.log("ChildAreaレンダリング");
return (
<>
{open ? (
<div>
<p>子コンポーネント</p>
</div>
) : null}
</>
);
});
子コンポーネントをmemo化することでpropsに変更があった時のみレンダリングが起きるようにできる。
基本的にコンポーネントは全てmemo化するべき。
useCallback(関数をmemo化)
import { useState } from "react";
import { ChildArea } from "./components/ChildArea";
export const App = () => {
const [text, setText] = useState("");
const [open, setOpen] = useState(false);
const onChangeText = (e) => setText(e.target.value);
const onClickOpen = () => setOpen(!open);
// 追加
const onClickClose = () => setOpen(false);
return (
<div className="App">
<input value={text} onChange={onChangeText} />
<br />
<br />
<button onClick={onClickOpen}>表示</button>
<ChildArea open={open} onClickClose={onClickClose} />
</div>
);
};
import { memo } from "react";
export const ChildArea = memo((props) => {
const { open, onClickClose } = props;
console.log("ChildAreaレンダリング");
return (
<>
{open ? (
<div>
<p>子コンポーネント</p>
</div>
) : null}
<button onClick={onClickClose}>閉じる</button>
</>
);
});
今までの機能に表示を閉じるボタンを実装するためにonClickClose
という関数を追加した。
その場合、先程memo化したChildAreaには再度再レンダリングが起きるようになってしまう。
なぜなら、アロー関数で書いた関数は毎回新しい関数を生成しているという判断をされてしまい、propsが違うものとして扱われ、propsが更新された場合と同様に再レンダリングが起きてしまう。
useCallbackを使う
useEffectと同様に第二引数に入れた値を監視するので今回はsetOpen
を指定して監視しsetOpen
の処理が走った時のみレンダリングが起きる。
結果、不要な再レンダリングを防げる。
const onClickClose = useCallback(() => setOpen(false),[setOpen]);
基本コンポーネントはmemo化しmemo化したコンポーネントに関数を使う時は、
その関数にuseCallbackを使うことで不要な再レンダリングを防げるのでレンダリングを最適化できる。
参考