🔁

SWRの再取得を疎結合に通知する、イベント駆動のReact設計パターン

に公開

はじめに

ReactでSPAを開発していると、ある画面での操作が別のコンポーネントに影響を与えるという場面に頻繁に出くわします。

たとえば、以下のようなケースです:

  • 商品の並び替えが完了したら、商品一覧を再取得したい
  • アイテムの削除や追加後に、他のビューのデータも更新したい

こうしたときに、SWRの mutate() を使えばうまくいきそうに思えますが、実際にはいくつかの課題があります。

この記事では、Contextや状態管理ライブラリに頼らず、依存を増やさずに実現できる方法として、超軽量なEventEmitterパターンをご紹介します。

mutateの限界:"グローバルに呼べばOK" ではない

SWRの mutate(key) を使うと、指定キーのキャッシュを更新できます。

import { mutate } from 'swr'

mutate('/api/products')

しかし、以下のような課題もあります:

  • useSWRInfinite 使用時には unstable_serialize(不安定なAPI)が必要
  • キーが動的な場合、正確に再現するのが難しい
  • キー構造が少しでも変わると mutate() が効かないことがある

つまり、「グローバルmutateで更新できるから大丈夫」とはいかない場面も少なくありません。

他の選択肢とその課題

EventTarget を使う

ブラウザ標準APIで気軽に使えますが、以下のような懸念があります:

  • CustomEvent の構文が冗長
  • イベント名を文字列で管理するため壊れやすい
  • TypeScriptで型安全を担保するのが難しい

✅ 状態管理ライブラリ(Zustand, Jotaiなど)

もちろん選択肢のひとつですが:

  • SWRのキャッシュとグローバルストアの責務が重複しがち
  • 小さな通知のためにストアを導入するのは過剰

mitt のようなPub/Subライブラリ

軽量でシンプルな良ライブラリです。もし自作しない場合は素直にこれを使うのも良い選択です。

ただ今回は、依存ゼロ・型安全・シンプルな自作のEventEmitterを選びました。

解決策:数行で作れる超軽量EventEmitter

以下のように自作すれば、依存なしで型安全かつ簡潔にイベント通知ができます。

app/lib/events/createEventEmitter.ts

export function createEventEmitter<T = void>() {
  const listeners = new Set<(payload: T) => void>()

  return {
    emit(payload: T) {
      listeners.forEach((fn) => fn(payload))
    },
    subscribe(fn: (payload: T) => void): () => void {
      listeners.add(fn)
      return () => { listeners.delete(fn) }
    },
  }
}

使い方:emitとsubscribeをつなぐだけ

イベントの定義

// app/events/index.ts
import { createEventEmitter } from '~/lib/events'

export const userCreated = createEventEmitter<{ id: number }>()

購読(たとえば useSWR の中で)

const { data, mutate } = useSWR('/users', fetcher)

useEffect(() => {
  const unsubscribe = userCreated.subscribe(() => {
    mutate()
  })
  return unsubscribe
}, [mutate])

発火(たとえばユーザー作成完了時)

userCreated.emit({ id: newUser.id })

ディレクトリ構成例

app/
├── lib/
│   └── events/
│       ├── createEventEmitter.ts
│       └── index.ts
├── events/
│   ├── users.ts
│   └── index.ts

eventsは汎用的すぎて適していないかもしれないのですが、他におもいつかない上、特にこれ以外の用途で使わなかったためeventsとしています。

おわりに

React + SWR の環境では、軽量なイベント通知のニーズが意外と多いものです。

  • 状態管理ライブラリを使うほどでもない
  • 親子関係がないためPropsではつなげられない
  • グローバルmutateも使いづらい

そんなとき、このEmitterパターンは非常に有効です。

また、この仕組みはSWRに限らず、任意の非同期処理や副作用の通知にも活用できます。ぜひ試してみてください。

Discussion