toshi-toma blog

主にフロントエンド、作業ログあとは色々なメモ ✍️ 🍅

コンポーネント内でコンポーネントを生成する際の問題

以下のような、コンポーネント内でコンポーネントを生成するようなコードについての話。

function App() {
  const Child = () => {
    return <div>child</div>
  }
  return (
    <div className="App">
      <Child />
    </div>
  );
}

Reactは再レンダリングの際に、要素が同じtypeかを===でチェックして、違うtypeなら、対象のコンポーネントツリーを削除して、新しく生成するらしい。 例えば、<a>から<img>は違うtype。<Article>から<Comment>も違うtype。コンポーネントの場合、参照が違うなら、違うtype。

reactjs.org

特に気にしてなかったけど、「コンポーネント内でコンポーネントを生成する際」に問題になることがある。

以下のコードは、毎回、Childコンポーネントを新しく生成するので、typeの比較で違う参照となる。毎回コンポーネントツリーを削除して、生成していることになる。

function App() {
  const Child = () => {
    return <div>child</div>
  }
  return (
    <div className="App">
      <Child />
    </div>
  );
}

なので、基本的には以下のように、コンポーネントの参照が1度だけ生成されるようにするのが良い。

const Child = () => {
    return <div>child</div>
  }
function App() {
  return (
    <div className="App">
      <Child />
    </div>
  );
}

そして、Stateを持つ場合に特に問題になる。

以下のコードでは、Appコンポーネントの中で、InnerChildコンポーネントを生成している。

const App = () => {
  const [count, setCount] = React.useState(0);

  const InnerChild = () => {
    const [innerCount, setInnerCount] = useState(0);
    return <button onClick={() => setInnerCount((s) => s + 1)}>InnerChild: {innerCount}</button>;
  };

  return (
    <div>
      <button onClick={() => setCount((s) => s + 1)}>{count}</button>
      <Child />
      <InnerChild />
    </div>
  );
};

const Child = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((s) => s + 1)}>Child: {count}</button>;
};

Appコンポーネントが持つsetCountが呼ばれるたびに、Appコンポーネントは再レンダリングされ、その子コンポーネントであるInnerChildコンポーネントやChildコンポーネントも再レンダリングされる。

この時に、InnerChildコンポーネントは、参照が変わり、typeが違うことになる。この結果、Stateがリセットされてしまう。

コンポーネントの外で定義した、Childコンポーネントは、参照が変わらないので、同じtypeと判断され、再レンダリング時もstateが保持される。カウントがリセットされることはない。

これは、Custom Hooksでコンポーネントを返すパターンを実装してると、知らず知らずのうちに、問題になることがありそう。ただ、InnerChildコンポーネントの生成にReact.useMemoを使うと、Stateが保持されたので、それで大丈夫なのかもしれない。

以下のredditで、ReactコアチームのDan Abramovが問題についてコメントしてる。

www.reddit.com

以下の記事でも「Component Types and Reconciliation」という項目で説明している。

blog.isquaredsoftware.com