Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】🚀 Gestión de estado en React

2025年09月13日に「Dev.to」が公開したITニュース「🚀 Gestión de estado en React」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Reactアプリの状態管理は、useStateだけでは難しい場合がある。React標準のuseContext+useReducerと、大規模アプリ向け業界標準のRedux(Hooks版)の2パターンがある。前者は小~中規模、後者は複雑な状態や高度なデバッグが必要な大規模アプリ向きだ。アプリの規模で使い分け、最初はReact標準から試すのがよい。

出典: 🚀 Gestión de estado en React | Dev.to公開日:

ITニュース解説

Reactアプリケーションにおける状態管理は、アプリケーションの規模が大きくなるにつれて複雑さが増す。基本的な状態管理にはReactのuseStateフックが便利だが、状態が複数のコンポーネントで共有される場合や、状態の更新ロジックが複雑になる場合、useStateだけでは対応が難しくなることがある。この記事では、Reactの組み込み機能であるuseContextuseReducerを組み合わせた方法と、業界標準のライブラリであるReduxをHooksと組み合わせて使う方法の二つについて解説する。

まず、Reactの組み込み機能を利用するパターンとして、useContextuseReducerを組み合わせた方法がある。これは、外部ライブラリを追加せずに、Reactの機能だけで複雑な状態を管理し、アプリケーション全体で共有するための強力なパターンである。この方法は、比較的小規模から中規模のアプリケーションに適していると言える。具体的にこの方法が適しているのは、状態がuseStateだけでは管理しきれないほど複雑になった場合、多くのコンポーネントが状態の更新ロジックを共有する必要がある場合、そして「プロップドリリング」と呼ばれる、不要なコンポーネントを経由してプロップを深く渡していく問題を避けたい場合だ。また、プロジェクトに新しい依存関係を追加したくない場合にも有効である。

このパターンの中心にあるのは、useReduceruseContextの二つのフックである。useReducerは、状態のすべてのロジックを「リデューサー関数」と呼ばれる一つの場所に集中させる。このリデューサー関数は、現在の状態と受け取った「アクション」に基づいて新しい状態を計算する役割を担う。一方、useContextは、このリデューサーによって管理される状態と、状態を更新するための「ディスパッチ関数」を、必要とするどんな子コンポーネントにも手動でプロップとして渡すことなく提供する。

具体的な例として、ショッピングカート機能を考えてみよう。まず、カートの状態(商品リストや合計金額など)、アイテム追加や削除といったカート操作のアクション定義、そしてこれらのアクションに応じてカートの状態を更新するリデューサー関数を含むCartContext.jsというファイルを作成する。このファイル内で、useReducerを使って状態とディスパッチ関数を管理し、createContextで作成したCartContext.Providerを通じてそれらをアプリケーションに提供する。次に、アプリケーションのエントリポイントとなるApp.jsのようなファイルで、CartProviderコンポーネントを使って、カートの状態にアクセスする必要があるすべてのコンポーネントをラップする。これにより、CartProviderの子孫コンポーネントはどこからでもカートの状態にアクセスできるようになる。最後に、useCartというカスタムフックを作成し、これをProductListCartSummaryといったコンポーネント内で使用する。ProductListでは商品をカートに追加するためにディスパッチ関数を使い、CartSummaryではカートの現在の状態(アイテム数や合計金額)を読み取るために状態オブジェクトを利用する。このようにして、コンポーネントは直接プロップを受け取ることなく、カートの状態を簡単に操作・表示できるようになる。

このパターンの利点は、外部のライブラリに依存しないこと、状態変更のロジックが一箇所に集中するため予測可能になること、そしてコンポーネントが状態の読み取りとアクションのディスパッチに専念できるためコードがシンプルになることである。しかし、欠点も存在する。例えば、デフォルトではコンテキストの任意の部分が変更されると、そのコンテキストを消費しているすべてのコンポーネントが再レンダリングされてしまうため、大規模なアプリケーションではパフォーマンスの問題が生じる可能性がある。これにはuseMemoなどの手動での最適化が必要となる。また、Redux DevToolsのような高度なデバッグツールが利用できないため、状態の変化履歴を詳細に追跡することは難しい。

次に、より大規模なアプリケーション向けの状態管理ソリューションとして、ReduxとHooksを組み合わせた方法がある。Reduxは、JavaScriptアプリケーションで状態を管理するための最も人気のあるライブラリの一つであり、特に大規模なアプリケーションでその真価を発揮する。以前は「ボイラープレートコード」(定型的な繰り返しコード)が多いことで知られていたが、Redux Toolkit (RTK)の登場により、はるかにシンプルで直感的に使えるようになった。Reduxに移行すべきタイミングは、アプリケーションの状態が非常に大きく複雑で、UIの多くの部分で共有される場合、API呼び出しのような非同期処理を組織的に扱うための「ミドルウェア」ロジックが必要な場合(Redux ThunkやSagaなど)、デバッグが困難になり、タイムトラベルデバッグのような高度なツール(Redux DevTools)で状態の履歴やアクションを検査する必要がある場合、そしてuseContextだけではパフォーマンスの最適化が追いつかなくなった場合である。Reduxは不要な再レンダリングを防ぐために高度に最適化されている。

Redux Toolkitを使ったショッピングカートの実装も見てみよう。まず、@reduxjs/toolkitreact-reduxという二つのパッケージをインストールする。次に、「スライス」と呼ばれる概念を導入する。Redux Toolkitでは、createSlice関数を使って、状態の一部(この場合はカート)に関連するリデューサーとアクションを一つのファイルにまとめることができる。このスライスでは、カートの初期状態を定義し、addItemremoveItemclearCartといったリデューサー関数を記述する。Redux ToolkitはImmerというライブラリを内部で利用しているため、これらのリデューサー関数内で直接状態を「ミューテート」(変更)するようなコードを記述しても安全に動作する。また、createSliceは、定義したリデューサーから対応するアクションクリエーターを自動的に生成してくれるため、手動でアクションを定義する手間が省ける。

スライスを作成したら、次にReduxストアを設定する。ストアは、アプリケーション全体のすべての状態を保持するグローバルなオブジェクトである。configureStore関数を使って、作成したカートスライス(cartReducer)をストアに追加する。ストアが設定できたら、react-reduxから提供されるProviderコンポーネントを使い、アプリケーション全体をこのストアでラップする。これはuseContextの場合のCartProviderと同様の役割を果たす。最後に、コンポーネントからReduxストアにアクセスするために、useSelectoruseDispatchという二つのフックを利用する。useSelectorフックはReduxストアから特定のデータを選択して読み取るために使い、選択したデータが変更された場合にのみコンポーネントが再レンダリングされるように最適化されている。useDispatchフックは、Reduxストアにアクションをディスパッチするために使う。ProductListコンポーネントではuseDispatchを使ってaddItemアクションをディスパッチし、CartSummaryコンポーネントではuseSelectorを使ってカートのアイテム数と合計金額を読み取る。

useContextuseReducerのパターンとRedux Toolkit with Hooksを比較すると、いくつかの重要な違いが浮かび上がる。依存関係の面では、前者はReactに組み込まれており外部ライブラリ不要だが、後者は@reduxjs/toolkitreact-reduxが必要である。理想的なアプリケーションの規模としては、前者は小規模から中規模、後者は中規模から大規模なアプリケーションでグローバルな複雑な状態を管理するのに適している。パフォーマンスに関しては、前者は良い性能を持つものの、不要な再レンダリングを防ぐには手動での最適化(useMemoなど)が必要になる場合がある。一方、Reduxは高度に最適化されており、コンポーネントは選択したデータが変更された場合にのみ再レンダリングされる。デバッグツールでは、前者は特別なツールがないが、Reduxには高度なデバッグ機能を持つRedux DevToolsがあり、状態の履歴やアクションの検査、タイムトラベルデバッグなどが可能だ。ミドルウェアのサポートは、前者はネイティブには対応していないが、Reduxでは非同期ロジックを整理するRedux Thunkがデフォルトで含まれるなど、強力にサポートされている。ボイラープレートコードは、前者はコンテキスト、リデューサー、プロバイダーの作成が必要で中程度だが、Redux Toolkitを使えば最小限に抑えられる。しかし、スライスやアクション、ストアといった新しい概念を学ぶ必要があり、学習曲線は中程度であると言える。

結論として、どちらのパターンを選ぶべきかについては、現代的な推奨事項がある。まず、常にReactの基本的なツールから始めることが推奨される。useStateから始め、状態が複雑になってきたらuseReducerにリファクタリングする。そして、その状態を複数のコンポーネントで共有する必要が生じたら、useContextuseReducerを組み合わせる方法を導入する。Reduxは、「もしもに備えて」最初から導入するのではなく、Reduxが解決する問題、例えば複雑な状態のデバッグ、高度な非同期処理の管理、またはuseContextに関連するパフォーマンスのボトルネックに直面したときに初めて導入を検討すべきである。適切なツールを選択することで、アプリケーションの管理性と拡張性を大きく向上させることができるだろう。

関連コンテンツ