⬛️

Reactの基本のあれこれを`console.log()`で確認してみた

2022/08/14に公開約6,300字

Click 👉 to read the article in English.https://takuyakikuchi.tech/i-checked-this-and-that-of-react-basics-with-consolelog

console.logしてみた

1. 親と子でのState更新と、再レンダリング

確認事項

  • 親と子のstateが変更される際の、再レンダリングの発生を確認する。

コード

  • 親コンポーネント:App
  • 子コンポーネント:
    • ChildA(Appからpropsを受け取る)
      • countstateを持つ
    • ChildB(Appからpropsを受け取らない)
const ChildA = ({ state }) => {
  const [count, setCount] = React.useState(0);
+ console.log(`rendering in child A component: count has ${count}`);
  return (
    ...
      <button onClick={() => setCount(count + 1)}>Child A: Count-up</button>
    ...
  );
};
const ChildB = () => {
+ console.log("rendering in child B component");
  return <div>Child B doesn't have props passed from the parent</div>;
};
export default function App() {
  const [state, setState] = React.useState(false);
+ console.log("rendering in parent component");
  return (
    <div className="App">
      ...
      <button onClick={() => setState(!state)}>Update the parent state</button>
      ...
      <ChildA state={state} />
      ...
      <ChildB />
    </div>
  );
}

コンソールログ

Console
<!-- 1. Initial rendering -->
rendering in parent component 
rendering in child A component: count has 0 
rendering in child B component 
<!-- 2. Update the parent state -->
rendering in parent component 
rendering in child A component: count has 0 
rendering in child B component 
<!-- 3. Update the child A state -->
rendering in child A component: count has 1 
<!-- 4. Update the parent state -->
rendering in parent component 
rendering in child A component: count has 1 
rendering in child B component 

確認したこと

  • 親コンポーネントのstateが変更された際に、propsの受け渡しの有無に関わらず、親、子どちらのコンポーネントでも再レンダリングが発生する。(No.2参照)
  • 子コンポーネントでのstateの変更は、そのコンポーネントだけ再レンダリングが発生する。(No.3参照)
  • 親コンポーネントの再レンダリングによる、子コンポーネントの再レンダリングでは子コンポーネントのstateは最新が維持される。(No.4参照)

サンドボックス

2. useState初期値 vs 遅延初期化

確認事項

  • stateの遅延初期化が初期レンダリング時の時のみ、呼び出されていることを確認する。
  • 一方、初期値は再レンダリング毎に呼び出されていることを確認する。

React: Lazy initial state

コード

  • 親コンポーネント:App
  • 子コンポーネント:Child
    • childStateA state: 遅延初期化
    • childStateB state: 初期値
const someExpensiveCalculation = (number, type) => {
+ console.log(`in the ${type} initial state`);
  return number * 10;
};
const Child = ({ number }) => {
  const [childStateA, setChildStateA] = React.useState(() => {
    return someExpensiveCalculation(number, "lazy");
  });
  const [childStateB, setChildStateB] = React.useState(
    someExpensiveCalculation(number, "default")
  );
  console.log(
+   `rendering in child component: A: ${childStateA}, B: ${childStateB}`
  );
  return (
    <>
      <p>{`The childStateA is ${childStateA}`}</p>
      <button onClick={() => setChildStateA(childStateA + 1)}>
        Child A: Count-up
      </button>
      <p>{`The childStateB is ${childStateB}`}</p>
      <button onClick={() => setChildStateB(childStateB + 1)}>
        Child B: Count-up
      </button>
    </>
  );
};
export default function App() {
  const [state, setState] = React.useState(false);
  return (
    <div className="App">
      <button onClick={() => setState(!state)}>Update the parent state</button>
      <Child number={10} />
    </div>
  );
}

コンソールログ

Console
<!-- 1. Initial rendering -->
in the lazy initial state 
in the default initial state 
rendering in child component: A: 100, B: 100 
<!-- 2. Parent state update -->
in the default initial state 
rendering in child component: A: 100, B: 100 
<!-- 3. Child state A update -->
in the default initial state 
rendering in child component: A: 101, B: 100 
<!-- 3. Child state B update -->
in the default initial state 
rendering in child component: A: 101, B: 101 
<!-- 4. Parent state update -->
in the default initial state 
rendering in child component: A: 101, B: 101 

確認したこと

  • 遅延初期化を行えば、初期レンダリング時のみsomeExpensiveCalculation()が呼ばれていて、再レンダリングでは無視される。
  • 一方、遅延初期化でなく値を渡したときには、再レンダリングが走るたびsomeExpensiveCalculation()が呼ばれている。

サンドボックス

3. useEffectのタイミング

確認事項

  • useEffectに渡された関数はレンダーの結果が画面に反映された後に動作することを確認する。

React: useEffect

コード

  • stateを依存する値としているuseEffectにて、dataをfetch後、messageステートを更新する。
const dataFetchMock = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("setMessage executed in useEffect");
  }, 1500);
});
export default function App() {
  const [message, setMessage] = React.useState();
  const [state, setState] = React.useState(false);
  React.useEffect(() => {
+   console.log(`in useEffect. state: ${state}`);
    dataFetchMock.then((value) => {
      setMessage(value);
    });
  }, [state]);

+ console.log(`rendering: just before return jsx. message: ${message}`);
  return (
    <div className="App">
      <button onClick={() => setState(!state)}>Update the parent state</button>
      <p>{message === undefined ? "undefined" : message}</p>
    </div>
  );
}

コンソールログ

Console
<!-- 1. Initial rendering -->
rendering: just before return jsx. message: undefined 
in useEffect. state: false 
rendering: just before return jsx. message: setMessage executed in useEffect 
<!-- 2. State(dependency of the useEffect) updated -->
rendering: just before return jsx. message: setMessage executed in useEffect 
in useEffect. state: true 
rendering: just before return jsx. message: setMessage executed in useEffect 

確認したこと

  • useEffectはレンダーの後に動作している。
    • 初期レンダリングで(No.1参照)、まずはレンダリング => useEffect => useEffect内でのmessageステートの変更で再度レンダリング
    • useEffectの依存配列に含まれているstateの更新(No.2参照)では、stateの更新によるレンダリング => useEffect => useEffect内でのmessageステートの変更で再レンダリング

サンドボックス

まとめ

なんとなくの理解で使うことのできるReact。
だけど、再レンダーのタイミングなど、自分で確認してみることは有意義だと思った。

GitHubで編集を提案

Discussion

ログインするとコメントできます