Reduxは、JavaScriptアプリケーションのための予測可能な状態コンテナです。Reactと一緒に使用されることが多いですが、Reactに限定されるものではありません。Reduxは、アプリケーションの状態を一元管理することで、アプリケーションの動作をより予測可能に、安定して、そしてデバッグしやすくします。
Reduxの主な原則は以下の通りです:
-
シングルソースオブトゥルース(単一の信頼できる情報源): アプリケーションの全状態は、一つのストア内のオブジェクトツリーに保存されます。これにより、状態の管理が容易になり、デバッグやテストが簡単になります。
-
状態は読み取り専用: 状態を変更する唯一の方法は、何かが起こったことを示すアクションを発行することです。これにより、ビューまたはコールバックが状態を直接変更することがなくなり、バグや不整合が減少します。
-
変更は純粋な関数によって行われる: アクションによって状態ツリーがどのように変更されるかを指定するために、純粋なリデューサー関数を使用します。リデューサーは、前の状態とアクションを取り、次の状態を返します。
Reduxの流れは以下のようになります:
-
アクションの発行: アプリケーションで何かが起こるたびに、アクションが作成されます。アクションは、発生したイベントのタイプと更新が必要なデータを示すプレーンなオブジェクトです。
-
リデューサーの実行: アクションが発行されると、Reduxのリデューサー関数が呼び出されます。この関数は、現在の状態とアクションを引数として受け取り、新しい状態を返します。
-
ストアの更新: リデューサー関数によって返された新しい状態がストアに保存されます。
-
ビューの更新: ストアの状態が変更されると、UIが新しい状態を反映するように更新されます。
Reduxは、状態管理を中央集中化し、アプリケーションの動作を予測可能にすることで、大規模なアプリケーションや高度にインタラクティブなアプリケーションの開発を容易にします。
実装例
Reduxの基本的な実装例を以下に示します。この例では、シンプルなカウンターアプリケーションを考え、その動作に必要なアクション、リデューサー、そしてストアの設定について説明します。
まず、必要なReduxのライブラリをインストールします。
npm install redux
次に、アクションタイプ、アクションクリエーター、そしてリデューサーを定義します。
// actions.js
// アクションタイプの定義
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
// アクションクリエーターの定義
export const increment = () => ({
type: INCREMENT,
});
export const decrement = () => ({
type: DECREMENT,
});
// reducers.js
import { INCREMENT, DECREMENT } from './actions';
// リデューサーの定義
export const counterReducer = (state = 0, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
次に、Reduxストアを作成し、リデューサーを渡します。
// store.js
import { createStore } from 'redux';
import { counterReducer } from './reducers';
// ストアの作成
export const store = createStore(counterReducer);
最後に、UIコンポーネントを作成し、ストアと接続します。
// App.js
import React from 'react';
import { store } from './store';
import { increment, decrement } from './actions';
class App extends React.Component {
componentDidMount() {
// ストアの変更を購読し、状態の変更があるたびにUIを更新
store.subscribe(() => this.forceUpdate());
}
handleIncrement = () => {
store.dispatch(increment());
};
handleDecrement = () => {
store.dispatch(decrement());
};
render() {
return (
<div>
<h1>カウント: {store.getState()}</h1>
<button onClick={this.handleIncrement}>増加</button>
<button onClick={this.handleDecrement}>減少</button>
</div>
);
}
}
export default App;
このコードは、Reduxの基本的なパターンを示しています。アプリケーションが成長するにつれて、複数のリデューサーを組み合わせたり、非同期アクションを扱うためのミドルウェア(例:redux-thunk)を使用するなど、より複雑なパターンが必要になる場合があります。
ユースケース
Reduxは、JavaScriptアプリケーション用の予測可能な状態コンテナです。Reactと組み合わせて使用すると、アプリケーションの状態を効果的に管理し、コンポーネント間で状態を共有することができます。以下に、ReactのReduxの一般的なユースケースをいくつか示します。
-
大規模なアプリケーションの状態管理:
- 大規模なプロジェクトや、多くの状態を持つアプリケーションでは、Reduxを使用してアプリケーションの状態を一元管理することができます。
-
複雑な状態のロジック:
- アプリケーションに複数の状態が存在し、それらが互いに依存している場合、Reduxはその複雑さを抽象化し、状態の更新をより予測可能にします。
-
パフォーマンスの最適化:
- Reduxは、必要に応じて再レンダリングするコンポーネントを選択することで、パフォーマンスの最適化に役立ちます。
-
状態の永続性と再現性:
- Reduxを使用すると、アプリケーションの状態を保存し、必要に応じて以前の状態に戻すことができます。これは、特定の状態をデバッグしたり、アプリケーションの状態を保存して後で再開したりする場合に便利です。
-
コミュニティとミドルウェアのサポート:
- Reduxは大規模なコミュニティに支えられており、ロギング、エラー報告、非同期API呼び出し(redux-thunkやredux-sagaなど)といった多くのミドルウェアが利用可能です。
これらのユースケースは、Reduxが提供する状態管理の柔軟性と予測可能性を示しています。ただし、小規模なプロジェクトや状態管理がそれほど複雑でない場合は、ReactのContext APIやReact Queryなどの他のソリューションを検討するのも良いでしょう。
ReactのReduxはAngularだったらどの機能にあたる?
Angularアプリケーションにおける状態管理のための機能は、"NgRx"です。NgRxはReduxの原則に基づいており、Angularアプリケーションのための状態管理ライブラリです。
ReactのReduxと同様に、NgRxもまたFluxアーキテクチャに触発されており、アプリケーションの状態を一元管理するストア、状態を変更するためのアクション、そしてアクションに応じてストアの状態を変更するリデューサーという概念を使用します。
NgRxの主な特徴は以下の通りです:
-
Observableに基づく: NgRxはRxJSを利用しており、ストアの状態に対する変更をObservableとして提供します。これにより、非同期操作やストリーム処理が容易になります。
-
Immutability(不変性): NgRxでは、状態は変更可能なオブジェクトではなく、変更されるたびに新しいオブジェクトが作成されます。これにより、パフォーマンスの最適化や変更の追跡が容易になります。
-
Effects(エフェクト): 非同期処理や副作用のあるアクションを扱うための仕組みです。これにより、API呼び出しやルーティングなどの副作用を効果的に管理できます。
-
DevToolsのサポート: Reduxと同様に、NgRxもまた開発ツールを提供しており、リアルタイムでの状態の監視、アクションのディスパッチ、過去の状態へのタイムトラベルなどが可能です。
-
Entity Management: NgRx entityは、コレクションの形でのデータ管理を簡素化するユーティリティを提供します。
これらの機能により、NgRxはAngularアプリケーションにおける複雑な状態管理、データ処理、およびサイドエフェクトの管理に適しています。
AngularでのNgRxの実装例
AngularでReduxのような状態管理を実装するには、通常、NgRxライブラリを使用します。以下に、簡単なNgRxを使用した状態管理の実装例を示します。この例では、単純なカウンターアプリケーションを考え、その状態をインクリメント(増加)またはデクリメント(減少)するアクションを実装します。
まず、NgRxをプロジェクトにインストールする必要があります。コマンドラインで以下のコマンドを実行します:
ng add @ngrx/store
1. アクションの定義
最初に、アプリケーションで使用するアクションを定義します。actions
フォルダを作成し、その中にcounter.actions.ts
ファイルを作成します:
// counter.actions.ts
import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter] Increment');
export const decrement = createAction('[Counter] Decrement');
export const reset = createAction('[Counter] Reset');
2. リデューサーの作成
次に、これらのアクションに応じて状態を変更するリデューサーを作成します。reducers
フォルダを作成し、その中にcounter.reducer.ts
ファイルを作成します:
// counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from '../actions/counter.actions';
export const initialState = 0;
const _counterReducer = createReducer(
initialState,
on(increment, (state) => state + 1),
on(decrement, (state) => state - 1),
on(reset, (state) => 0)
);
export function counterReducer(state, action) {
return _counterReducer(state, action);
}
3. ストアの設定
アプリケーションモジュールにストアを設定します。app.module.ts
に以下を追加します:
// app.module.ts
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './reducers/counter.reducer';
@NgModule({
// ...
imports: [
// ...
StoreModule.forRoot({ count: counterReducer }),
],
// ...
})
export class AppModule {}
4. コンポーネントでのストアの使用
最後に、コンポーネントでストアを使用して状態を取得し、アクションをディスパッチします。例えば、app.component.ts
では以下のようになります:
// app.component.ts
import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import { increment, decrement, reset } from './actions/counter.actions';
@Component({
selector: 'app-root',
template: `
<button (click)="increment()">Increment</button>
<div>Current Count: {{ count$ | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
count$: Observable<number>;
constructor(private store: Store<{ count: number }>) {
this.count$ = store.pipe(select('count'));
}
increment() {
this.store.dispatch(increment());
}
decrement() {
this.store.dispatch(decrement());
}
reset() {
this.store.dispatch(reset());
}
}
このコードでは、increment
、decrement
、reset
メソッドがそれぞれのアクションをディスパッチし、count$
Observableは非同期パイプを使用してテンプレート内で現在のカウントを表示します。
これはNgRxを使用した非常に基本的な例ですが、実際のアプリケーションでは、エフェクト(副作用の管理)、セレクター(状態の一部を選択的に取得)、エンティティ(コレクションの管理)など、より高度な機能を使用することが多いです。