Vue 3 における Vuex 4 を活用した状態管理の構築

Vuex 4 による集中状態管理の概要

Vue 3 アプリケーションにおいて、複数のコンポーネント間で共有されるデータを効率的に扱うためには、Vuex 4 の導入が有効です。Vuex はアプリケーション全体の状態を単一のストアに集約し、変更履歴を予測可能な形で管理するためのパターンを提供します。

コアコンセプトと役割

Vuex ストアを構成する主要な要素は以下の通りです。

  • state:アプリケーションの現状を保持するデータオブジェクトです。数値、文字列、オブジェクト、配列などあらゆる形式のデータを格納できます。
  • getters:ストア内の state から派生したデータを計算するために使用されます。Vue コンポーネントの computed プロパティに類似しており、データを加工してコンポーネントに提供します。
  • mutations:state を直接変更する唯一の手段です。各 mutation はタイプ名とハンドラ関数で構成され、同期処理のみを実行する必要があります。非同期処理を含めると状態変化の追跡が困難になるため注意が必要です。
  • actions:非同期処理や複雑なロジックを担当します。API リクエストなどの処理を行い、完了後に mutations をコミットして state を更新します。
  • modules:ストアを機能単位で分割するための仕組みです。各モジュールは独自の state、mutations、actions、getters を持ち、大規模なアプリケーションでも管理性を維持できます。

状態管理を導入することで、コンポーネント間の props 渡しを減らし、ロジックとビューの分離を促進し、コードの保守性を向上させることができます。

環境構築とストアの初期化

プロジェクトに Vuex 4 をインストールするには、以下のコマンドを実行します。

npm install vuex --save

次に、アプリケーションのエントリーポイントでストアインスタンスを生成します。ここでは商品在庫数を管理する例を示します。

import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      quantity: 0
    }
  },
  mutations: {
    increase(state) {
      state.quantity++
    },
    decrease(state) {
      state.quantity--
    }
  },
  actions: {
    loadQuantityAsync({ commit }) {
      setTimeout(() => {
        commit('increase')
      }, 1000)
    }
  },
  getters: {
    currentQuantity(state) {
      return state.quantity
    }
  }
})

export default store

この設定では、createStore 関数を用いてストアを定義しています。初期状態として quantity を 0 に設定し、増減させるための mutation と、遅延実行される action を用意しています。

コンポーネントでの利用

Vue コンポーネント内では、useStore フックを使用してストアにアクセスします。Composition API を利用した実装例は以下の通りです。

<template>
  <div>
    <h2>在庫数:{{ quantity }}</h2>
    <button @click="add">追加</button>
    <button @click="remove">削除</button>
    <button @click="loadAsync">非同期追加</button>
  </div>
</template>

<script>
import { defineComponent, useStore } from 'vue'

export default defineComponent({
  setup() {
    const storeInstance = useStore()

    const add = () => {
      storeInstance.commit('increase')
    }

    const remove = () => {
      storeInstance.commit('decrease')
    }

    const loadAsync = () => {
      storeInstance.dispatch('loadQuantityAsync')
    }

    return {
      quantity: storeInstance.getters.currentQuantity,
      add,
      remove,
      loadAsync
    }
  }
})
</script>

上記のコードでは、storeInstance 変数にストアを格納し、それぞれの操作に対応した関数を定義しています。getters を通じて状態を参照し、mutations や actions をトリガーすることで状態を更新します。

モジュール分割による構成

機能が複雑化した場合、ストアをモジュールに分割することが推奨されます。以下は在庫管理モジュールを分離した例です。

import { createStore } from 'vuex'

const inventoryModule = {
  state() {
    return {
      quantity: 0
    }
  },
  mutations: {
    increase(state) {
      state.quantity++
    },
    decrease(state) {
      state.quantity--
    }
  },
  actions: {
    asyncIncrement(context) {
      setTimeout(() => {
        context.commit('increase')
      }, 1000)
    }
  },
  getters: {
    doubledQuantity(state) {
      return state.quantity * 2
    }
  }
}

const store = createStore({
  modules: {
    inventory: inventoryModule
  }
})

export default store

このようにモジュールを定義し、ストア登録時に modules オプションで紐付けることで、名前空間を持たせた状態管理が可能になります。

Mutations の実装パターン

状態変更を行う mutations には、いくつかの一般的な実装パターンがあります。

1. 数値の増減

mutations: {
  addQuantity(state) {
    state.quantity++;
  },
  subtractQuantity(state) {
    state.quantity--;
  }
}

2. 値の更新

mutations: {
  setProductName(state, newName) {
    state.productName = newName;
  }
}

3. 状態のリセット

mutations: {
  clearState(state) {
    state.quantity = 0;
    state.productName = '';
  }
}

4. オブジェクト状態の部分的更新

mutations: {
  updateProfile(state, newProfile) {
    state.profile = { ...state.profile, ...newProfile };
  }
}

5. 配列状態の操作

mutations: {
  addToCart(state, item) {
    state.cartItems.push(item);
  },
  removeFromCart(state, index) {
    state.cartItems.splice(index, 1);
  }
}

6. ペイロードを使用したデータ受け渡し

mutations: {
  updateSetting(state, payload) {
    state.settingValue = payload.value;
  }
}

これらのパターンは、アプリケーションの要件に応じて組み合わせて使用します。mutations 内では必ず同期処理のみを行い、非同期ロジックが必要な場合は actions を経由させることが重要です。

タグ: Vue3 vuex4 state-management composition-api frontend-architecture

5月25日 16:39 投稿