Reactで状態を管理する:RxJS、Redux、Context、MobX、Zustand、Recoil、カスタムフックの比較
React における RxJS と Redux/Context の比較:コンポーネントとハンドラメソッドにおける状態管理とアクセス
Reactアプリケーションにおいて、コンポーネント間で状態を共有し、ハンドラメソッドから状態にアクセスすることは重要な課題です。状態管理には様々なアプローチがありますが、RxJSとRedux/Contextはよく利用される2つの選択肢です。この記事では、それぞれの仕組みと利点・欠点、そして使い分けについて分かりやすく解説します。
RxJS
RxJSはReactiveXと呼ばれるライブラリに基づいた、非同期データストリーム処理のためのライブラリです。状態管理においては、Observableと呼ばれるデータストリームを用いて状態の変化を通知します。コンポーネントはObserverと呼ばれる購読者としてObservableを購読し、状態の変化を監視することができます。
利点
- 柔軟性が高い
- 非同期処理に適している
- シンプルで理解しやすい
欠点
- デバッグが難しい
- 複雑な状態管理には向いていない
Redux
Reduxは単一方向データフローアーキテクチャに基づいた、集中型状態管理ライブラリです。Storeと呼ばれる中央ストアに状態を保持し、Actionと呼ばれるイベントによって状態を更新します。コンポーネントはStoreに接続することで状態にアクセスし、Dispatchと呼ばれる関数を用いてActionを発行することができます。
- タイムトラベル機能
- デバッグがしやすい
- 予測可能な状態変化
- 過剰な設定が必要になる場合がある
- 複雑で習得に時間がかかる
Context
ContextはReact v16.8で導入された、コンポーネント間で共有変数を簡単に共有するためのAPIです。Providerと呼ばれるコンポーネントで共有変数を定義し、Consumerと呼ばれるコンポーネントで共有変数にアクセスすることができます。
- 導入が簡単
- シンプルで軽量
- テストが難しい
- グローバルスコープのため、誤って状態を書き換えてしまう可能性がある
使い分け
- 軽微な共有変数の共有にはContext
- 複雑な状態管理が必要で、予測可能性とデバッグ性を重視する場合はRedux
- シンプルで非同期処理が多い場合はRxJS
RxJS、Redux、Contextはそれぞれ異なる利点と欠点を持つため、状況に合わせて適切なものを選択することが重要です。それぞれの仕組みを理解し、アプリケーションの要件に合わせて最適なツールを選びましょう。
RxJS を使用した状態管理
import React, { useState, useEffect } from 'react';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
const initialState = { count: 0 };
const store = Observable.of(initialState);
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const subscription = store.subscribe((state) => setCount(state.count));
return () => subscription.unsubscribe();
}, []);
const increment = () => {
store.next({ count: count + 1 });
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
import React from 'react';
import { createStore } from 'redux';
const initialState = { count: 0 };
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
};
const store = createStore(reducer);
const Counter = () => {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
Context を使用した状態管理
import React, { useState, createContext } from 'react';
const CountContext = createContext({ count: 0, increment: () => {} });
const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<CountContext.Provider value={{ count, increment }}>
{children}
</CountContext.Provider>
);
};
const Counter = () => {
const { count, increment } = useContext(CountContext);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default function App() {
return (
<CounterProvider>
<Counter />
</CounterProvider>
);
}
- Context
Providerと呼ばれるコンポーネントで共有変数を定義し、Consumerと呼ばれるコンポーネントで共有変数にアクセスすることができます。 - RxJS
Observableを使用して状態をストリームとして公開し、コンポーネントはObserverとして購読して状態の変化を監視します。
- 状態管理ライブラリの選択は、アプリケーションの要件や開発者の好みによって異なります。
- 実際のアプリケーションでは、コンポーネントの再レンダリングを最適化するために、React Hooks (useState, useSelector, useDispatch) や Redux Thunks などのツールを使用することがあります。
MobXは、状態管理を簡素化するためのライブラリです。オブザーバブルベースのアプローチを採用しており、自動的に状態の変化を検出してコンポーネントを更新します。MobXは、特に複雑な状態管理が必要な場合に役立ちます。
例
import React from 'react';
import { observable, action } from 'mobx';
class CounterStore {
@observable count = 0;
@action increment() {
this.count++;
}
}
const store = new CounterStore();
const Counter = () => {
return (
<div>
<p>Count: {store.count}</p>
<button onClick={store.increment}>Increment</button>
</div>
);
};
export default Counter;
Zustand
Zustandは、MobXと同様のオブザーバブルベースのアプローチを採用した、軽量な状態管理ライブラリです。MobXよりもシンプルで使いやすいのが特徴です。
import React from 'react';
import { createStore, useStore } from 'zustand';
const store = createStore(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
}));
const Counter = () => {
const { count, increment } = useStore(store);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
Recoil
Recoilは、Facebookが開発した、Reactにおける状態管理のための公式ライブラリです。原子と呼ばれる小さな状態単位を管理し、それらを組み合わせることで複雑な状態を表現することができます。Recoilは、学習曲線が少し険しいですが、強力で柔軟な状態管理を実現できます。
import React from 'react';
import { atom, useRecoilState } from 'recoil';
const countState = atom({
key: 'count',
default: 0,
});
const Counter = () => {
const [count, setCount] = useRecoilState(countState);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default Counter;
カスタムフック
React Hooksを使用して、独自の状態管理ロジックを実装することもできます。これは、シンプルな状態管理であれば有効な方法ですが、複雑な状態管理になると難しくなる可能性があります。
import React, { useState } from 'react';
const useCounter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return { count, increment };
};
const Counter = () => {
const { count, increment } = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
Reactアプリケーションで状態を管理するには、様々な方法があります。それぞれの方法には長所と短所があるため、アプリケーションの要件や開発者の好みによって最適な方法を選択することが重要です。
- テスト: 状態管理ライブラリによっては、テストが難しい場合があります。テストのしやすさも、ライブラリを選択する際の考慮事項の一つです。
- チームコラボレーション: チームで開発を行う場合は、チームメンバーがどの状態管理ライブラリに慣れているかを考慮する必要があります。
- パフォーマンス: 状態管理ライブラリによっては、パフォーマンスに影響を与える可能性があります。特に、複雑なアプリケーションの場合は、パフォーマンスを考慮してライブラリを選択する必要があります。
javascript reactjs redux