🔁
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