【ITニュース解説】A detail guide on optimizing the re-rendering issue in React's Context API
2025年09月13日に「Dev.to」が公開したITニュース「A detail guide on optimizing the re-rendering issue in React's Context API」について初心者にもわかりやすく解説しています。
ITニュース概要
ReactのContext APIは、値の変更時にContextを利用する全てのコンポーネントが不必要に再レンダリングされ、性能が低下する問題がある。これを解決するには、子コンポーネントに必要なデータのみをpropsで渡し、`memo`でラップすることで、関係するコンポーネントだけを再レンダリングさせ、パフォーマンスを改善できる。
ITニュース解説
Reactは、Webアプリケーションのユーザーインターフェースを構築するための人気のあるJavaScriptライブラリである。Reactアプリケーションは「コンポーネント」と呼ばれる小さな部品を組み合わせて作られており、これらのコンポーネントがユーザーの操作やデータの変化に応じて画面を更新する仕組みを「再レンダリング」と呼ぶ。
アプリケーションが成長し、コンポーネント間で共有したいデータが増えてくると、そのデータの受け渡しが複雑になることがある。通常、親コンポーネントから子コンポーネントへデータを渡すには「props」という仕組みを利用する。しかし、コンポーネントツリーの深い階層にある子コンポーネントにデータを渡す場合、途中の全ての親コンポーネントがそのデータを受け取り、さらに子へ渡していく必要があり、この作業は「propsバケツリレー」と呼ばれる。Context APIは、この手間を省き、コンポーネントツリー内のどこからでも特定のデータを直接利用できるようにする便利な機能である。
しかし、このContext APIを中規模から大規模なアプリケーションで利用する際に、一つの一般的な問題が発生することがある。それは、コンテキスト内のデータ(「コンテキスト値」と呼ぶ)の一部が変更されただけでも、そのコンテキストを消費している「全ての」コンポーネントが再レンダリングされてしまうという問題である。たとえ、変更された部分を実際に使っていないコンポーネントであっても、再レンダリングの対象となってしまい、特に複雑なコンポーネントツリーを持つアプリケーションでは、パフォーマンスの低下を引き起こす原因となる。
なぜこのような現象が起こるのか。Reactは、コンポーネントがコンテキスト内のデータのどの「特定の部分」を利用しているかを自動的に追跡する仕組みを持っていないためである。Reactが知っているのは、あるコンポーネントがそのコンテキスト全体を使っているということだけである。したがって、コンテキスト値のいずれかの部分が変更されると、Reactはコンテキストを消費している全てのコンポーネントに対し、「コンテキスト値が変わったので、再レンダリングしてください」と通知してしまう。結果として、全てのコンポーネントが再レンダリングを実行してしまうのである。
この問題を具体的に確認するため、以下のコード例を見てみよう。ここでは、AppContextというコンテキストを作成し、Appコンポーネントがtheme(テーマ)、user(ユーザー)、settings(設定)の3つの情報を含む一つの大きなstateオブジェクトを管理している。このstateオブジェクト全体は、AppContext.Providerを通じて子コンポーネントに提供される。ThemeDisplay、UserProfile、SettingsPanelという3つの子コンポーネントは、それぞれuseContext(AppContext)フックを使ってこの大きなstateオブジェクト全体を受け取っている。
Appコンポーネントには、「Change theme」ボタンが配置されている。このボタンをクリックすると、stateオブジェクトの中のtheme.colorというプロパティだけが変更される。しかし、ブラウザの開発者ツールでコンソールを開いて確認すると、console.logの出力から、ThemeDisplayだけでなく、UserProfileとSettingsPanelも「re-rendered」というメッセージを表示していることがわかる。これは、userやsettingsの情報は全く変更されていないにも関わらず、全てのコンポーネントが再レンダリングされたことを明確に示している。Reactはstateオブジェクト全体の参照が変更されたと判断し、useContextを使っている全てのコンポーネントに再レンダリングを指示するため、このような現象が起きるのだ。
この不要な再レンダリングの問題を解決し、アプリケーションのパフォーマンスを最適化するにはどうすればよいか。一つの効果的な方法は、子コンポーネントを独立した単位に分割し、それぞれに「必要なコンテキストデータのみ」をpropsとして渡すこと、そしてその子コンポーネントをReactのmemo(メモ)機能でラップすることである。
最適化されたコードでは、まずAppコンポーネントは引き続きtheme、user、settingsを含む全体のstateを管理する。しかし、今回はAppContext.Providerを使ってコンテキスト全体を提供するのではなく、stateの各部分を個別に子コンポーネントに渡す。具体的には、ThemeContainer、UserContainer、SettingsContainerという新しい中間コンポーネントを導入する。これらのContainerコンポーネントは、Appからそれぞれstate.theme、state.user、state.settingsといった、自分が必要とするstateの一部のみをpropsとして受け取る。
さらに重要なのは、これらのContainerコンポーネントや、その中で実際にUIを表示するThemeDisplay、UserProfile、SettingsPanelといったコンポーネントが、全てmemoでラップされている点である。memoは、Reactに「このコンポーネントのpropsが前回と変わっていなければ、再レンダリングをスキップしてください」と指示する機能である。
例えば、「Change Theme」ボタンをクリックしてstate.theme.colorが変更された場合を考えてみよう。Appコンポーネントのstate全体が更新されると、App自体は再レンダリングされる。その際、ThemeContainerには新しいthemeオブジェクトがpropsとして渡されるため、memoはthemeのpropsが変更されたと判断し、ThemeContainerを再レンダリングする。そしてThemeContainer内部のThemeDisplayも新しいcolorとfontSizeのpropsを受け取り再レンダリングされる。
一方、UserContainerやSettingsContainerは、それぞれ受け取っているuserやsettingsというpropsが、オブジェクトとして参照が変わっていない(中身も変わっていない)ため、memoによってこれらのコンポーネントの再レンダリングはスキップされる。つまり、UserProfileやSettingsPanelは再レンダリングされないのである。同様に、「Change User」ボタンがクリックされればUserContainerとUserProfileだけが再レンダリングされ、他のコンポーネントは影響を受けない。
この最適化手法の鍵は、Reactがpropsの変更をより細かく比較できるようになる点にある。大きなstateオブジェクト全体を渡す代わりに、必要なデータの「スライス」(一部)だけを個別のコンポーネントに渡すことで、そのコンポーネントは、自分に関係のないデータが変更されても、自身のpropsが変わらない限り再レンダリングを避けることができる。そして、memoがその判断を助け、不要な再レンダリングを効率的に抑制するのである。
結論として、このアプローチにより、特定のデータが変更されたときに、そのデータに依存するコンポーネントのみが再レンダリングされるようになり、他の無関係なコンポーネントは再レンダリングがスキップされる。これにより、アプリケーションの描画処理が大幅に効率化され、特に大規模なアプリケーションにおけるパフォーマンス問題の改善に繋がる。システムエンジニアとしてアプリケーションの性能を向上させる上で、この再レンダリング最適化の理解と適用は非常に重要なスキルとなるだろう。