5 minute read

学んだこと

再レンダリングの仕組み

  • State の値を変更した際に再レンダリングを行い、処理をもう一度実行している。
  • 以下の処理の場合、on/off ボタンを押したときに
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(true);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
    setNum(num + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};
  • console でレンダリング回数に関して調べてみる。
  • この場合、console.log("--------App---------")が 2 回表示されていた。
    • 理由としては、開発時のみ 2 回レンダリングを行っているから。
    • render を呼び出す再に strictmode を付与している且つ、開発ビルドだと 2 回レンダリングが行われる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
  console.log("--------App---------")
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(true);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
    setNum(num + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};
  • コンポーネントも確認する。
    • コンポーネント内に console.log を配置し確認する。
    • on/off ボタンを押して state を更新する。
      • 結果は以下の通りだった。
        • --------ColorfulMessage---------'が 2 回表示された。
        • 理由としては、state を更新すると関連するコンポーネントも再レンダリングが実行される。
        • 再レンダリングの結果、App 内の処理が再度呼び出された。
        • ColorfulMessage を呼び出している箇所が 2 箇所あるため、その分 conosle.log が呼ばれた。
export const ColorfulMessage = (props) => {
  console.log('--------ColorfulMessage---------');
  const contentStyleA = {
    color: props.color,
    fontSize: '18px',
  };

  return <p style={contentStyleA}>{props.children}</p>;
};

import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
  console.log("--------App---------")
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(true);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
    setNum(num + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};

簡単なプログラムを作成する。

  • 3 の倍数のときは\(^o^)/を表示するプログラムを作成する。
    • 3 の倍数のときは、\(^o^)/を表示できているが、on/off ボタンを押しても反応しないバグが発生している。
    • num = 3 の場合 on/off ボタンを押すと以下の処理が行われる。
      • onClickToggle が呼ばれ、isShowFase = true のため、isShowFace = false になる。
      • 再レンダリングが実行される。
      • if(num % 3 === 0)で true になり、isShowFace   setIsShowFace(true)が処理される。
      • isShowFace   setIsShowFace(true)では isShowFase = false のため、setIsShowFace(true)が評価される。
      • 結果\(^o^)/が表示されたままになる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useState } from 'react';
export const App = () => {
  console.log('--------App---------');
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(false);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  // 3の倍数の場合は\(^o^)/を表示するように処理
  if (num > 0) {
    if (num % 3 === 0) {
      isShowFace || setIsShowFace(true);
    } else {
      isShowFace && setIsShowFace(false);
    }
  }

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};

バグを解決するためには…

  • useEffect を使う必要がある。

useEffect の挙動について知る

  • この場合、on/off ボタンを押すと、------useEffect---------が呼び出されていた。
  • そのため、state の更新タイミングで useEffect が呼ばれている事がわかる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
  console.log('--------App---------');
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(false);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  useEffect(() => {
    console.log('------useEffect---------');
  });

  if (num > 0) {
    if (num % 3 === 0) {
      isShowFace || setIsShowFace(true);
    } else {
      isShowFace && setIsShowFace(false);
    }
  }

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};

  • useEffect に空の配列を渡してみる。
    • 最初のレンダリングは一律で呼び出される。
    • すると、最初のレンダリングは useEffect を呼び出しているが、state 更新タイミングでは呼び出されていなかった。
    • なので、再レンダリング時に都度呼び出したくない場合は、空の配列を渡す必要がありそう。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
  console.log('--------App---------');
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(false);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  useEffect(() => {
    console.log('useEffect');
  }, []);

  if (num > 0) {
    if (num % 3 === 0) {
      isShowFace || setIsShowFace(true);
    } else {
      isShowFace && setIsShowFace(false);
    }
  }

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};

  • useEffect の配列に num を渡すとどうなるか
    • 最初のレンダリング時は一律で呼び出される。
    • state の num が更新されるタイミングで--------useEffect---------が呼び出されていた。
    • useEffect に state を渡すと、state が変更のあったタイミングで呼び出されるようになる。
import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
  console.log('--------App---------');
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(false);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  useEffect(() => {
    console.log('--------useEffect---------');
  }, [num]);

  if (num > 0) {
    if (num % 3 === 0) {
      isShowFace || setIsShowFace(true);
    } else {
      isShowFace && setIsShowFace(false);
    }
  }

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};

useEffect を使って on/off の挙動を修正する

  • useEffect 内に 3 の倍数時の判定処理を入れるようにした。

    • isShowFace を変更しても、3 の倍数時の判定処理を通らなくなった。
    • 理由としては、useEffect 内の処理は num の更新タイミングのみ処理が実行されるため isShowFace が更新されても useEffect の処理は行われない。
 import { ColorfulMessage } from './components/ColorfulMessage';
import { useEffect, useState } from 'react';
export const App = () => {
  console.log('--------App---------');
  const [num, setNum] = useState(0);
  const [isShowFace, setIsShowFace] = useState(false);
  const onClickButton = () => {
    setNum((prev) => prev + 1);
  };
  const contentStyleA = { color: 'blue', fontSize: '18px' };
  const contentStyleB = { color: 'green', fontSize: '18px' };
  const onClickToggle = () => {
    setIsShowFace(!isShowFace);
  };

  useEffect(() => {
    if (num > 0) {
      if (num % 3 === 0) {
        isShowFace || setIsShowFace(true);
      } else {
        isShowFace && setIsShowFace(false);
      }
    }
  }, [num]);

  return (
    <>
      <h1 style={contentStyleA}>こんにちは!</h1>
      <ColorfulMessage color="blue">お元気ですか?</ColorfulMessage>
      <ColorfulMessage color="green">元気です!</ColorfulMessage>
      <button onClick={onClickButton}>カウントアップ</button>
      <p>{num}</p>
      <button onClick={onClickToggle}>on/off</button>
      {isShowFace && <p>\(^o^)/</p>}
    </>
  );
};

Tags:

Updated: