Shadow DOMでIslands Architectureっぽく(状態管理編)

に公開

記事の内容

前回、Shadow DOMでIslands Architectureっぽく(CSS編)という記事を記載しました。今回はShadow DOMにある複数のアプリ間での共有したい状態の管理方法について記載します。

動作環境

簡単なカウンターアプリを作ってみました。

https://igara.github.io/multi-fw-demo/nextjs/multi_counter.html

apps

今回のアプリも前回と同様で
Next.jsのSSGしたページをGitHub Pagesに公開し、ページ内にNext.js、Shadow DOM上にReactとVue2が動作しています。

解説

複雑な説明になるので図にしました。

状態共有の仕組み図

上記の図でアンダーバーがあるテキスト箇所はリンクになっていますので、該当箇所を見ることができます。

今回は@multi-fw-demo/shared-stateというCustomEventをPub/Subした状態管理ライブラリを作成しました。
CustomEventによるPub/Subの実装にした意図として複数アプリケーションで別々のビルドファイルを扱うものになるのでimportして使用するモジュールのリソースが別物になってしまうのでCustomEventに逃す意図があります。
余談によくある状態管理ライブラリとの違いを記載します。

この仕組みは株式会社カケハシさんの登壇した資料の爆速でプロダクトをリリースしようと思ったらマイクロフロントエンドを選んでいたと被ったとサンプルのカウンターアプリを作った後で気づきました。

より実践的で戦略的な思想について
型とテストで守るカスタムイベント通信 - 実プロダクトでの実装事例
から色々学ぶことができました。

データフロー図

  1. ユーザーアクション: いずれかのアプリ(Vue2/React/Next.js)でカウンターボタンをクリック
  2. ローカル更新: そのアプリのSharedStateStoreインスタンスが状態を更新
  3. グローバル通知: window.dispatchEvent()でCustomEventを発火
  4. クロスコンテキスト同期: すべてのShadowRoot内のSharedStateStorewindow.addEventListener()でイベントを受信
  5. UI更新: 各フレームワークの仕組み(Vue2のリアクティブシステム、ReactのuseSyncExternalStore)でUIが自動更新

余談

状態管理ライブラリってただのPub/Subの実装やったんや

漠然と状態管理の実装して、元々の状態管理どんな実装しているのか気になったというのと、前回のブログでもいっていたのですがどうしてもAstroがわいてきてChatGPTに相談してみました。

ChatGPTに聞いてみたこと

ChatGPT

初手でnanostoresというワード出したのもAstroのグローバルステートライブラリ推しっぽそうなので一番グローバルステートしてそう(どのアプリケーションでも使用できる意味合いで)という偏見で聞いてみました。

よくある状態管理ライブラリのPub/Sub実装

🟦 例:最もシンプルな Pub/Sub
const listeners = []

function subscribe(callback) {
  listeners.push(callback)
  return () => {
    const i = listeners.indexOf(callback)
    if (i !== -1) listeners.splice(i, 1)
  }
}

function publish(data) {
  listeners.forEach(cb => cb(data))
}

ChatGPTの例を見てなるほどとなり、今まで状態管理ライブラリのコードを読んだことなかったのですが、確かにこの形のものが多かったです。

参考

GitHubで編集を提案
chot Inc. tech blog

Discussion