0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Reactの再レンダリングについて

Last updated at Posted at 2021-10-19

前置き

Reactを学習し始めたときに理解が浅かったmemo化についてまとめてみました。

再レンダリングとは

変更を検知してコンポーネントを再処理することです。
画面をリロードしたわけでもないのに、処理によって画面が変更されるのは、コンポーネントが再レンダリングされている為です。

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

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

  1. stateが更新された時
  2. Propsが変更された時
  3. 再レンダリングされたコンポーネント配下の子コンポーネント全て

3は中々イメージがつきにくいですが、以下の例のようにルートコンポーネントであるApp.jsxがレンダリングされるとその配下のコンポーネントが全てレンダリングされてしまいます。
変更する必要のないコンポーネントまでレンダリングされてしまうと、アプリ自体のパフォーマンスが落ちてしまいます。
スクリーンショット 2021-10-19 20.06.11.png

React.memo, useCallbackを使って不要なレンダリングを防ぐ

ここから実際にコードを使って解説します。今回の例では、App.jsを親、子コンポーネントをChild.jsxとします。

// App.js
import { useState } from "react";
import Child from "./Child";
import "./styles.css";

export default function App() {
  const [number, setNumber] = useState(0);
  const onClickNumber = () => setNumber(number + 1);
  console.log("レンダリングしてます");
  return (
    <div className="App">
      <button onClick={onClickNumber}>ボタン</button>
      <h1>{number}</h1>
      <Child />
    </div>
  );
}

// Child.jsx
const Child = () => {
  return (
    <>
      {console.log("child")}
      <div>childです</div>
    </>
  );
};
export default Child;

親コンポーネントにあるボタンをクリックすると、子供であるChild.jsxに仕込んだconsole.logもクリックする度に発火してしまいます。
不要なレンダリングが起きているので、子コンポーネントは変更があった場合のみレンダリングさせたいです。

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

React.memo

コンポーネント自体をmemoで囲うだけでpropsに変更がない限りそのコンポーネントで再レンダリングが起きなくなります。

// child.jsx

// 追加
import { memo } from "react";

// memoで囲うだけ!
const Child = memo(() => {
  return (
    <>
      {console.log("child")}
      <div>childです</div>
    </>
  );
});
export default Child;

ボタンをクリックしても子コンポーネントにあるconsole.logが発火しなくなりました。

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

useCallback

子コンポーネントにアロー関数を渡すと、memo化していてもレンダリングされるようになります。
これをuseCallbackを活用することで一度定義したアロー関数が更新されない限り、同じものを使い回すという処理を実現できます。

// 書き方
useCallback(アロー関数, [依存配列]);

試しにボタンをクリックすると、子コンポーネントを表示非表示するような実装に変更します。

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

その際、親コンポーネントにあるアロー関数をpropsで渡してあげます。

// App.js
import { useState } from "react";
import Child from "./Child";
import "./styles.css";

export default function App() {
  const [number, setNumber] = useState(0);
  const [show, setShow] = useState(false);
  const onClickNumber = () => setNumber(number + 1);
  // 追加
  const onClickShow = () => setShow(!show);
  const onClickClose = () => setShow(false);

  return (
    <div className="App">
      <button onClick={onClickNumber}>ボタン</button>
      <h1>{number}</h1>
      <button onClick={onClickShow}>show/hide</button>
      {/* アロー関数をpropsで渡す */} 
      <Child show={show} onClickClose={onClickClose} />
    </div>
  );
}

// Child.jsx
import { memo } from "react";

const Child = memo((props) => {
  const { show, onClickClose } = props;
  return (
    <>
      {console.log("child")}
      {show ? (
        <>
          <div>childです</div>
          <button onClick={onClickClose}>閉じる</button>
        </>
      ) : null}
    </>
  );
});
export default Child;

コンソールを見ると子コンポーネントにあるconsole.logが再び発火してることが分かります。
memo化したにも関わらず、親コンポーネントにあるボタンをクリックすると、関係のない子コンポーネントも再びレンダリングされるようになってしまいました。
ezgif.com-gif-maker (9).gif

これはアロー関数がレンダリングされる度に再生成されるので、毎回違う関数として認識されてしまいます。
その為、propsが変わっていると判断されるので子コンポーネントも再レンダリングがおきるようになります。

処理の流れとしては以下のようになります。

  1. 親にあるボタンをクリックする
    2. stateが変更される為、親コンポーネントがレンダリングされる
  2. レンダリングすると親コンポーネントのアロー関数が再生成される
  3. 再生成したアロー関数を子コンポーネントに受け渡す
    5. propsが変更されていると判断される為、子コンポーネントもレンダリングされる

これをuseCallbackを使うことで、不要なレンダリングを防ぐことができます。

// App.js
// 追加
import { useState, useCallback } from "react";
import Child from "./Child";
import "./styles.css";

export default function App() {
  const [number, setNumber] = useState(0);
  const [show, setShow] = useState(false);
  const onClickNumber = () => setNumber(number + 1);
  const onClickShow = () => setShow(!show);
// 追加
  const onClickClose = useCallback(() => setShow(false), [setShow]);

  return (
    <div className="App">
      <button onClick={onClickNumber}>ボタン</button>
      <h1>{number}</h1>
      <button onClick={onClickShow}>show/hide</button>
      <Child show={show} onClickClose={onClickClose} />
    </div>
  );
}

これで不要なレンダリングを防ぐことができました!

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

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?