2 minute read

学んだこと

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

  • state が更新されたコンポーネント
  • props が変更されたコンポーネント
  • 再レンダリングされたコンポーネント配下の子要素は再レンダリング
    • 例としてコンポーネント A、コンポーネント B、コンポーネント C が存在すると過程する。
      • コンポーネント A の中に、コンポーネント B が存在する
      • コンポーネント B の中に、コンポーネント C が存在する。
    • コンポーネント B の State が更新された場合
      • コンポーネント A の再レンダリングはスキップされるが、コンポーネント B とコンポーネント C の再レンダリングが発生する。

最適化 1

最適化前のコード

  • <input value={text} onChange={onChangeText} />のテキストボックスに入力するとどうなるか
    • onChangeText で textState に変更がかかるため再レンダリング発生
    • 親コンポーネントが再レンダリング発生したため、子コンポーネントである ChildArea の再レンダリング発生
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
import { ChildArea } from './ChildArea';

export default function App() {
  const [text, setText] = useState('');
  const [open, setOpen] = useState(false);

  const onChangeText = (e) => setText(e.target.value);

  const onClickOpen = () => setOpen(!open);
  const onClickCountUp = () => {
    setCount(count + 1);
  };

  return (
    <div className="App">
      <input value={text} onChange={onChangeText} />
      <br />
      <br />
      <button onClick={onClickOpen}>表示</button>
      <ChildArea open={open} />
    </div>
  );
}

最適化を行う

  • 子コンポーネントである ChildArea にmemoで囲むこと
    • memo で囲むことで、ChildArea は親コンポーネントの影響による再レンダリングが起きない。
    • 基本的に、作成したコンポーネントにはmemoで囲むことを推奨する。
import { memo } from 'react';

const style = {
  width: '100px',
  height: '200px',
  backgroundColor: 'khaki',
};

export const ChildArea = memo((props) => {
  const { open } = props;
  console.log('ChildAreaがレンダリングされた!');

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log('...');
  });

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

最適化 2

最適化前

  • ChildArea コンポーネントを memo にて親コンポーネントが再レンダリング時に、ChildArea コンポーネントも再レンダリングが行われないように実装した。
  • しかし、form に文字を入力すると、ChildArea コンポーネントが再レンダリングされてしまった。

App.jsx

import { useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
import { ChildArea } from './ChildArea';

export default function 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>
  );
}

ChildArea.jsx

import { memo } from 'react';

const style = {
  width: '100px',
  height: '200px',
  backgroundColor: 'khaki',
};

export const ChildArea = memo((props) => {
  const { open, onClickClose } = props;
  console.log('ChildAreaがレンダリングされた!');

  const data = [...Array(2000).keys()];
  data.forEach(() => {
    console.log('...');
  });

  return (
    <>
      {open ? (
        <div style={style}>
          <p>子コンポーネント</p>
          <button onClick={onClickClose}>閉じる</button>
        </div>
      ) : null}
    </>
  );
});

原因

  • const onClickClose = () => setOpen(false);のようにアロー関数を用いて関数を呼び出すと、毎回新しい関数を生成していると判断される。そのため、毎回違う関数を呼び出していることとなる。
  • 処理の流れ
    1. form にて文字を入力
    2. setText が呼び出され、ステート更新する
    3. App コンポーネント再レンダリング
    4. onClickClose 関数が再生成される
    5. 再生成した onClickClose 関数を ChildArea の props に渡す
    6. ChildArea 再レンダリング

解決策

  • useCallbackを使用し onClickClose 関数は再生成せず、同じ関数を使用するようにする。
import { useState, useCallback } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
import { ChildArea } from './ChildArea';

export default function App() {
  const [text, setText] = useState('');
  const [open, setOpen] = useState(false);

  const onChangeText = (e) => setText(e.target.value);

  const onClickOpen = () => setOpen(!open);

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

  return (
    <div className="App">
      <input value={text} onChange={onChangeText} />
      <br />
      <br />
      <button onClick={onClickOpen}>表示</button>
      <ChildArea open={open} onClickClose={onClickClose} />
    </div>
  );
}

Tags:

Updated: