1
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 1 year has passed since last update.

Reactの再レンダリングの仕組み

Posted at

現在Reactを学んでいるWeb系への転職を目指している者です。
ポートフォリオをSPA化するために学んでいます。
自分用のメモのために残します。

再レンダリングされるときの条件

1.stateが更新されたコンポーネントは再レンダリングされる。
2.propsが変更されたコンポーネントは再レンダリングされる。
3.再レンダリングされたコンポーネント配下の子要素はすべて再レンダリングされる。

大きくこの3つの条件でレンダリングされます。
検証のためにはcodesandboxを使用します。

1.stateが更新されたコンポーネントは再レンダリングされる。

再レンダリングされるとconsole.logでレンダリングされましたというのが表示されるようなコードを作成しました。

App.jsx
import "./styles.css";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};
export default function App() {
  const [text, setText] = useState("");
  const onChangeText = (event) => setText(event.target.value);

  console.log("レンダリング");
  return (
    <>
      <div style={style}>
        <input
          style={textBox}
          value={text}
          onChange={onChangeText}
          placeholder="文字を入力してください"
        />
      </div>
    </>
  );
}

このコードではテキストボックスに文字入力されるたびにstateが更新されます。
画面を表示した状態ではレンダリングは1回されているので、コンソールにはレンダリングの文字が1回表示されます。
スクリーンショット 2022-01-30 20.52.46.png
今回は文字を5回入力したので、stateが5回更新されます。
表示したときのものと合わせて計6回コンソールにレンダリングの文字が表示されます。
スクリーンショット 2022-01-30 20.53.04.png

2.propsが更新されたコンポーネントは再レンダリングされる。

先ほどのテキストボックスをコンポーネント化しました。
改修したコードはこちらになります。

TextBox.jsx
const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};

export const TextBox = (props) => {
  const { text, onChangeText } = props;
  console.log("TextBoxコンポーネントが更新された");
  return (
    <div style={style}>
      <input
        style={textBox}
        value={text}
        onChange={onChangeText}
        placeholder="文字を入力してください"
      />
    </div>
  );
};
App.jsx
import "./styles.css";
import { TextBox } from "./components/TextBox";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

export default function App() {
  const [text, setText] = useState("");
  const onChangeText = (event) => setText(event.target.value);

  console.log("レンダリング");
  return (
    <>
      <h1 style={style}>レンダリングの条件を理解する</h1>
      <TextBox text={text} onChangeText={onChangeText} />
    </>
  );
}

この状態で文字を入力すると子コンポーネントであるTextBox.jsxに記述した「TextBoxコンポーネントがレンダリングされた」というコンソールも表示されていることがわかります。
スクリーンショット 2022-01-30 21.14.33.png

3.再レンダリングされたコンポーネント配下の子要素はすべて再レンダリングされる。

ここからが注意しなければならない部分になります。
まずは新たなコンポーネントを作成します。

ChildArea.jsx
const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};

export const TextBox = (props) => {
  const { text, onChangeText } = props;
  console.log("TextBoxコンポーネントがレンダリングされました");
  return (
    <div style={style}>
      <input
        style={textBox}
        value={text}
        onChange={onChangeText}
        placeholder="文字を入力してください"
      />
    </div>
  );
};

新たなコンポーネント作成に伴い既存のコードも改修したので、載せます。

TextBox.jsx
const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const textBox = {
  width: "100%"
};

export const TextBox = (props) => {
  const { text, onChangeText } = props;
  console.log("TextBoxコンポーネントがレンダリングされました");
  return (
    <div style={style}>
      <input
        style={textBox}
        value={text}
        onChange={onChangeText}
        placeholder="文字を入力してください"
      />
    </div>
  );
};
App.js
import "./styles.css";
import { TextBox } from "./components/TextBox";
import { ChildArea } from "./components/ChildArea";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

const buttonStyle = {
  margin: "20px",
  padding: "20px"
};

export default function App() {
  const [text, setText] = useState("");
  const [display, setDisplay] = useState(false);
  const onChangeText = (event) => setText(event.target.value);
  const onClickChanged = () => setDisplay(!display);
  console.log("レンダリング");
  return (
    <>
      <h1 style={style}>レンダリングの条件を理解する</h1>
      <TextBox text={text} onChangeText={onChangeText} />
      <button style={buttonStyle} onClick={onClickChanged}>
        表示
      </button>
      <br />
      <ChildArea display={display} />
    </>
  );
}

これにより表示ボタンを押すと画像が表示される機能が追加されました。

画像を表示させる。
スクリーンショット 2022-01-31 17.13.57.png

画像を非表示させる。
スクリーンショット 2022-01-31 17.13.36.png

ここでの問題というのは再レンダリングされたコンポーネントの子コンポーネントはすべて再レンダリングされてしまいます。
コンソールを確認してみると。

・文字をテキストボックスに入力した場合
スクリーンショット 2022-01-31 17.17.10.png

・表示ボタンをクリックした場合
スクリーンショット 2022-01-31 17.17.28.png

どちらも関係ないコンポーネントまで再レンダリングされてしまいます。
要するにすべてのコンポーネントが呼び出されています。

子要素がすべて再レンダリングされる場合の解決方法。

コンポーネントをmemoで囲む。
memoで囲まれた関数はpropsが渡された時のみ、再レンダリングされるという指定がされます。

memo化の方法

ChildArea.jsx
// memo化に必要
import { memo } from "react";
const imageStyle = {
  height: "100px",
  width: "100px"
};

// 関数をmemo()で囲んであげればmemo化が完成。
export const ChildArea = memo((props) => {
  const { display } = props;
  console.log("ChildAreaがレンダリングされました");
  return (
    <>
      {display ? (
        <img style={imageStyle} src="images/account.jpeg" alt="Logo" />
      ) : null}
    </>
  );
});

今回はchildAreaコンポーネントをmemo化しました。
これで文字を入力した場合にChildAreaコンポーネントが再レンダリングされることはないはずです。
確認してみると。スクリーンショット 2022-01-31 17.26.29.png

試しの文字を二回入力しました。
ページが開かれた際に呼び出されたのみで、再レンダリングはされていないことがわかります。
これでpropsが渡されたときのに再レンダリングされる機能の実装ができました。

memo化の注意点

memo化したコンポーネントに関数を渡す場合は、渡す関数は再レンダリングの際に再生成されるpropsと判断されるためmemo化がうまく機能しなくなります。
検証するために表示ボタンもChildAreaコンポーネントに組み込みます。

ChildArea.jsx
import { memo } from "react";

const imageStyle = {
  height: "100px",
  width: "100px"
};
const buttonStyle = {
  margin: "20px",
  padding: "20px"
};

export const ChildArea = memo((props) => {
  const { display, onClickChanged } = props;
  console.log("ChildAreaがレンダリングされました");
  return (
    <>
      <button style={buttonStyle} onClick={onClickChanged}>
        表示
      </button>
      <br />
      {display ? (
        <img style={imageStyle} src="images/account.jpeg" alt="Logo" />
      ) : null}
    </>
  );
});
App.js
import "./styles.css";
import { TextBox } from "./components/TextBox";
import { ChildArea } from "./components/ChildArea";
import React, { useState } from "react";

const style = {
  margin: "20px",
  padding: "20px",
  backgroundColor: "pink"
};

export default function App() {
  const [text, setText] = useState("");
  const [display, setDisplay] = useState(false);
  const onChangeText = (event) => setText(event.target.value);
  const onClickChanged = () => setDisplay(!display);
  console.log("レンダリング");
  return (
    <>
      <h1 style={style}>レンダリングの条件を理解する</h1>
      <TextBox text={text} onChangeText={onChangeText} />
      {/* 表示ボタンをChildAreaコンポーネントに追加したため関数をpropsとして渡しています。 */}
      <ChildArea display={display} onClickChanged={onClickChanged} />
    </>
  );
}

このコードで注目して欲しいのは、ChildAreaコンポーネントに関数をpropsとして渡しているということです。
関数をpropsでコンポーネントに渡すとmemo化がうまく働かなくなります。
試しに文字を入力した際のコンソールを確認してみます。
スクリーンショット 2022-01-31 17.40.01.png

memo化したはずなのに再レンダリングが起きてしまっています。

関数をpropsで渡した際にmemo化がうまく動いていない原因

原因は新たなpropsが渡されていると判断されてしまっているからです。
なぜなら、関数はレンダリングされるたびに再生成されていると判断されているため、同じ名前の関数でもレンダリングされる前とは別の関数として認識されてしまうからです。
そのため、memoがうまく機能しません。

改善方法

useCallBackを使用する。
memo化したコンポーネントに渡す関数を、useCallBackで囲むことで、第二引数に指定した配列の値が変化する時のみ再生成されると設定できる。
配列が空の場合は最初のレンダリング時から再生成されなくなります。
それでは、useCallbackで関数を囲みます。

App.js
const onClickChanged = useCallback(() => setDisplay(!display), [display]);

これにより変数であるdisplayが変化する時のみonClickChangedが再生成されるように設定できました。
それではまた、試しに文字を入力した際のコンソールを確認してみます。
スクリーンショット 2022-01-31 17.52.43.png
今回はしっかりとmemo化が行われていることがわかります。

まとめ

Reactを扱う際に基本的な部分だったと思いますが、基本が大切だと思うのでメモを残しました。
この記事が私と同じくReactを勉強し始めた人の助けになったらうれしいです。

1
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
1
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?