概要
モダンフロントエンドでは、React
に Redux
や Recoil
などの状態管理を組み合わせるのが一般的である。
一方、RxJS
を導入することで、状態・非同期・副作用をストリームとして構造的に統一できる可能性が広がる。
本稿では、React
アプリにおいて RxJS
をどのように統合し、
従来の Redux
ベースの構造を より柔軟でテスト可能かつ予測可能なリアクティブ設計へ移行するか を解説する。
1. なぜReactにRxが必要なのか?
Reactはコンポーネント駆動であるが、
- 非同期の制御
- グローバルな副作用管理
- 状態依存のイベント駆動処理
には明確な標準が存在しない。
→ Reduxはこの課題の1つの解決策だが、非同期処理(thunk, saga, observable)に課題が残る
2. Redux-Observableという選択肢
// epics/index.ts
const fetchUserEpic = (action$) =>
action$.pipe(
ofType('FETCH_USER'),
switchMap(action =>
ajax.getJSON(`/api/users/${action.payload.id}`)
.pipe(map(user => ({ type: 'FETCH_USER_SUCCESS', payload: user })))
)
)
-
action$
= ストリームとしてのRedux Action - Reduxの副作用をRxJSで定義する → 非同期処理の合成・制御が容易
3. ReactとRxの最小統合:useObservable
パターン
import { useEffect, useState } from 'react'
import { BehaviorSubject } from 'rxjs'
const count$ = new BehaviorSubject(0)
export function useObservable<T>(observable$: BehaviorSubject<T>) {
const [value, setValue] = useState(observable$.getValue())
useEffect(() => {
const sub = observable$.subscribe(setValue)
return () => sub.unsubscribe()
}, [observable$])
return value
}
使用例
function Counter() {
const count = useObservable(count$)
return (
<>
<p>{count}</p>
<button onClick={() => count$.next(count + 1)}>+</button>
</>
)
}
4. 状態をSubjectで構造的に持つ
const user$ = new BehaviorSubject<User | null>(null)
const loading$ = new BehaviorSubject(false)
function loadUser(id: string) {
loading$.next(true)
ajax.getJSON(`/api/users/${id}`)
.pipe(finalize(() => loading$.next(false)))
.subscribe(user => user$.next(user))
}
→ 状態は明示的にストリームとして保持される
→ UIは useObservable(user$)
のように 購読的に同期される
5. Reduxとの併用か、完全移行か
手法 | 概要 | 推奨状況 |
---|---|---|
Redux Only | reducer/thunk/sagaで管理 | 状態が単純、非同期が少ない場合 |
Redux + Observable | actionをRxで拡張 | 既存Reduxに非同期機能を追加したい場合 |
RxJS Only | 全状態をSubject管理、Reactに直接バインド | 中~大規模SPA、状態粒度が高い場合 |
6. パターン集:Rxで状態と副作用を接続する
✅ 自動検索リクエスト
const search$ = new Subject<string>()
const result$ = search$.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(q => fetchSearch(q))
)
→ ユーザー入力を直接ストリーム化 → useObservable(result$)
で即反映
✅ ストア+UIの分離
// store/user.ts
export const user$ = new BehaviorSubject<User | null>(null)
export const setUser = (u: User) => user$.next(u)
// components/UserProfile.tsx
const user = useObservable(user$)
→ Reduxのようなストア構造をSubjectで実装可能
よくある誤解と対策
❌ Reduxの代わりにRxJSを使うのはやりすぎ?
→ ✅ 状態と非同期が密結合しているUIでは、Rxはむしろ過不足のない選択肢
❌ Reactの再レンダリングがRxと相性悪そう
→ ✅ Rxによって値の変化を明示的に制御できるため、むしろ余計な再レンダリングを回避できる
❌ Redux-Observableは難しい・冗長
→ ✅ ルール化・演算子に慣れれば、Sagaより明快に制御構造を記述できる
結語
Reactアプリにおける状態と副作用は、
ますます非同期・複雑化し、構造的制御が求められている。
RxJSはその要請に対し、
- 副作用の合成
- 状態のストリーム化
- イベントの構造的マッピング
という視点で、設計の中核を担う力を持つ。
リアクティブ統合設計とは、
“UIと状態とイベントの分離ではなく、統合されたストリーム構造として全体を再構成するための設計思想である。”