GitHub icon

関数内のコンポーネントとレンダーの違い

投稿日: 2025/5/2

Next.jsReact

Reactにおける「コンポーネントとして呼び出す」と「関数として実行する」違いによるレンダリングの挙動について解説します。

レンダリングの違い

Reactでは、「コンポーネントとして呼び出す」のと「関数として実行して要素を返す」場合とで、評価のされ方や再レンダリングの挙動が変わります。この記事では、その違いをコードとともに解説します。

コード例

以下は、コンポーネントと関数による描画の違いを示すサンプルコードです。

const InnerLog = memo(({ name }: { name: string }) => {
  useEffect(() => {
    console.log(`${name} rendered`);
  }, []);
  return <div>{name}</div>;
});
 
export default function Page() {
  const [count, setCount] = useState(0);
 
  const InnerComponent = useCallback(() => {
    return <InnerLog name="InnerComponent" />;
  }, [count]);
 
  const innerRender = useCallback(() => {
    return <InnerLog name="InnerRender" />;
  }, [count]);
 
  return (
    <div>
      <section>
        {/* コンポーネントとして呼び出す */}
        <InnerComponent />
      </section>
 
      <section style={{ marginTop: "2rem" }}>
        {/* 関数を実行して呼び出す */}
        {innerRender()}
      </section>
 
      <button onClick={() => setCount((c) => c + 1)}>
        Increment
      </button>
    </div>
  );
}

ボタンを押して count を更新すると、useCallback により定義された InnerComponent および innerRender は再生成されます。
しかし、挙動に差が出ます。

  • <InnerComponent /> は新しいコンポーネントとして解釈されるため、再レンダリングと再マウントを行います。
  • innerRender() は要素を返す関数として解釈されるため、返された要素(<InnerLog />)の再評価は発生せず、メモ化された結果を再利用します。

なぜ再レンダリングの挙動が異なるのか?

InnerComponent は、レンダリングごとに新しい関数として生成されます。Reactはこの変更を「異なるコンポーネント」と判断し、前回のコンポーネントをアンマウントして新しいものをマウントします。
これが、意図しない再レンダリングを引き起こします。

一方で、innerRender() はただの関数であり、呼び出された結果として返されるのはあくまでJSX要素(<InnerLog />)です。結果として、メモ化された<InnerLog />が返ります。

JSXでの評価の違い

それぞれの記述が、JSXでどのように変換されるかを見てみましょう。

<InnerComponent /> の場合

<InnerComponent />

これは、以下のように解釈されます。

React.createElement(InnerComponent, null)

InnerComponent は関数ですが、Reactはこれを「関数コンポーネント」とみなして処理します。

innerRender() の場合

{innerRender()}

この関数の返り値は<InnerLog name="InnerRender" />であるため、Reactは単にそれをレンダリングするだけです。
以下のように評価されます。

React.createElement(InnerLog, { name: "InnerRender" })

レンダーとして扱う方が良さそう

整理すると、<InnerComponent /> のように関数コンポーネント内に定義された関数をJSXでコンポーネントのように記述するのは避けるべきです。
Reactはそれを新しいコンポーネントと解釈し、意図しない再マウントの発生につながるためです。

もしその関数がUIロジックの一部としてJSX要素を返すだけであれば、関数を実行して使う(render() のように呼び出す)形式にしましょう。

{innerRender()}

戻り値を単なる要素として扱い、メモ化や差分検出が正しく機能し、不要な再描画を回避できます。

まとめ

コンポーネント内で関数を定義し、<InnerComponent /> のようにコンポーネント形式で使用すると、「新しいコンポーネント」として扱い、毎回再マウントを引き起こします。
意図しない副作用やパフォーマンスの低下につながる可能性があります。

そのため、

  • 関数コンポーネント内で定義した関数を、JSX内で <Component /> のように使うのは避ける。
  • UIロジックの一部として、ただJSX要素を返したいだけなら、関数として実行(例:innerRender())する。

このように使い分けることで、再レンダリング挙動をより正確に制御でき、不要な再マウントを防ぐことができます!