2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReduxやUIステートとの統合:React×RxJSによる設計実践

Posted at

概要

モダンフロントエンドでは、ReactReduxRecoil などの状態管理を組み合わせるのが一般的である。
一方、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と状態とイベントの分離ではなく、統合されたストリーム構造として全体を再構成するための設計思想である。”

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?