見出し画像

第六回:マイクロフロントエンドの状態管理

「ログイン状態を各アプリで共有したい...」
「カートに商品を追加したら、他のアプリにも反映させたい...」
「でも、アプリ間の結合度は下げたい...」

マイクロフロントエンドで悩ましいのが状態管理です。今回は、実践的な実装例を交えながら、効果的な状態管理の方法を見ていきましょう。

どんな状態を共有する必要があるの?

まず、共有が必要な状態の種類を整理してみましょう

// よくある共有状態の例
const sharedStates = {
  // ユーザー関連
  user: {
    id: "123",
    name: "山田太郎",
    isLoggedIn: true
  },
  
  // カート情報
  cart: {
    items: [],
    total: 0
  },
  
  // 全体の設定
  settings: {
    theme: "dark",
    language: "ja"
  }
};

3つの状態管理パターン

1. イベントベース方式

各アプリ間でカスタムイベントを使って通信する方法です。

// イベントバスの実装
class EventBus {
  constructor() {
    this.listeners = {};
  }

  // イベントの購読
  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  // イベントの発行
  emit(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(callback => callback(data));
    }
  }
}

// 使用例
const eventBus = new EventBus();

// カートアプリ
eventBus.on('productAdded', (product) => {
  updateCart(product);
});

// 商品一覧アプリ
function addToCart(product) {
  eventBus.emit('productAdded', product);
}

メリット

  • アプリ間の結合度が低い

  • 実装がシンプル

  • スケーラビリティが高い

デメリット

  • 状態の一貫性の担保が難しい

  • デバッグがやや複雑

2. 共有ストレージ方式

ブラウザのストレージを使って状態を共有する方法です。

// ストレージラッパーの実装
class SharedStorage {
  constructor() {
    this.storage = window.localStorage;
  }

  // 状態の保存
  setState(key, value) {
    this.storage.setItem(key, JSON.stringify(value));
    window.dispatchEvent(new Event(`storage:${key}`));
  }

  // 状態の取得
  getState(key) {
    const value = this.storage.getItem(key);
    return value ? JSON.parse(value) : null;
  }

  // 状態変更の監視
  onChange(key, callback) {
    window.addEventListener(`storage:${key}`, () => {
      callback(this.getState(key));
    });
  }
}

// 使用例
const storage = new SharedStorage();

// カートの状態管理
function updateCart(product) {
  const cart = storage.getState('cart') || { items: [] };
  cart.items.push(product);
  storage.setState('cart', cart);
}

// 状態変更の監視
storage.onChange('cart', (cart) => {
  updateCartUI(cart);
});

メリット

  • 実装が簡単

  • 永続化が容易

  • リロードしても状態が維持される

デメリット

  • パフォーマンスに注意が必要

  • 複雑な状態管理には向かない

3. 共有ライブラリ方式

状態管理ライブラリを共有する方法です。

// 共有のReduxストア
const sharedStore = {
  user: createSlice({
    name: 'user',
    initialState: { isLoggedIn: false },
    reducers: {
      login: (state) => { state.isLoggedIn = true },
      logout: (state) => { state.isLoggedIn = false }
    }
  }),
  
  cart: createSlice({
    name: 'cart',
    initialState: { items: [] },
    reducers: {
      addItem: (state, action) => {
        state.items.push(action.payload);
      }
    }
  })
};

// 各アプリでの使用
function ProductList() {
  const dispatch = useDispatch();
  const cart = useSelector(state => state.cart);

  const addToCart = (product) => {
    dispatch(sharedStore.cart.actions.addItem(product));
  };
}

メリット

  • 型安全性が高い

  • 状態管理が統一的

  • デバッグが容易

デメリット

  • アプリ間の結合度が高くなる

  • ライブラリのバージョン管理が必要

実践的なTips

1. 状態の種類に応じた使い分け

// 使い分けの例
const stateManagement = {
  // ユーザー情報:共有ストレージ
  user: new SharedStorage(),
  
  // リアルタイム性が必要な状態:イベントバス
  notifications: new EventBus(),
  
  // 複雑な状態:共有ライブラリ
  cart: sharedStore.cart
};

2. エラーハンドリング

class SafeEventBus extends EventBus {
  emit(event, data) {
    try {
      super.emit(event, data);
    } catch (error) {
      console.error(`Event ${event} failed:`, error);
      // フォールバック処理
    }
  }
}

まとめ

状態管理の方法は、要件によって使い分けることが重要です

  • シンプルな共有 → イベントベース

  • 永続化が必要 → 共有ストレージ

  • 複雑な状態管理 → 共有ライブラリ

次回は、マイクロフロントエンドにおけるテスト戦略について詳しく見ていきます。

いいなと思ったら応援しよう!