LoginSignup
4
2

More than 1 year has passed since last update.

[React]再レンダリングの仕組みと最適化について

Posted at

再レンダリング最適化について勉強した時のメモです。

再レンダリングが起きる条件

  • stateが更新されたとき。
  • propsが変更されたとき
  • 親コンポーネントが再レンダリングされた時のコンポーネント配下の子要素。

以下の場合Bが再レンダリングされるとCも再レンダリングされる。
スクリーンショット 2021-10-12 11.16.02.png

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>
  );
};

①.gif

この場合inputのテキストボックスに値を入れるたびにconsoleが走るのでstateが変わるたびに再レンダリングが発生している。

propsが更新された時

src/App.jsx
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>
  );
};
src/components/ChildArea.jsx
export const ChildArea = (props) => {
  const { open } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
};

②.gif
ChildAreaopenというpropsを渡しているのでボタンを押すたびに再レンダリングが起きている。

親コンポーネントが再レンダリングされた時

src/App.jsx
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>
  );
};
src/components/ChildArea.jsx
export const ChildArea = (props) => {
  const { open } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
};

③.gif

この場合ChildAreaのpropsであるopenには触れていないが、親コンポーネントに再レンダリングが起きているためChildAreaにも再レンダリングが起きている。

コンポーネント最適化

  • memo
  • useCallback

memo(コンポーネントをmemo化)

src/components/ChildArea.jsx
import { memo } from "react";

export const ChildArea = memo((props) => {
  const { open } = props;

  console.log("ChildAreaレンダリング");

  return (
    <>
      {open ? (
        <div>
          <p>子コンポーネント</p>
        </div>
      ) : null}
    </>
  );
});

④.gif

子コンポーネントをmemo化することでpropsに変更があった時のみレンダリングが起きるようにできる。
基本的にコンポーネントは全てmemo化するべき。

useCallback(関数をmemo化)

src/App.jsx
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>
  );
};
src/components/ChildArea.jsx
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>
    </>
  );
});

⑤.gif

今までの機能に表示を閉じるボタンを実装するためにonClickCloseという関数を追加した。
その場合、先程memo化したChildAreaには再度再レンダリングが起きるようになってしまう。
なぜなら、アロー関数で書いた関数は毎回新しい関数を生成しているという判断をされてしまい、propsが違うものとして扱われ、propsが更新された場合と同様に再レンダリングが起きてしまう。

useCallbackを使う

useEffectと同様に第二引数に入れた値を監視するので今回はsetOpenを指定して監視しsetOpenの処理が走った時のみレンダリングが起きる。
結果、不要な再レンダリングを防げる。

const onClickClose = useCallback(() => setOpen(false),[setOpen]);

ezgif.com-gif-maker (5).gif

基本コンポーネントはmemo化しmemo化したコンポーネントに関数を使う時は、
その関数にuseCallbackを使うことで不要な再レンダリングを防げるのでレンダリングを最適化できる。

参考

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2