Vuexで実現するVue.jsアプリの効率的な状態管理
株式会社アドバンテッジリスクマネジメント DX開発部の古堅です。
今回は、Vue.jsアプリケーションにおける状態管理の課題を解決する強力なライブラリ「Vuex」について紹介します。
背景
私は半年前、Vuexを活用しているプロジェクトに参画することになりました。それまで状態管理ライブラリの利用経験がなかったため、この機会に体系的に学習しました。本記事では、実際のプロジェクト経験と調査内容をもとに、Vuexの基本概念から実装方法まで記載していきます。
参考
公式ドキュメント:
Vuexとは
Vuexとは、Vue.jsアプリケーションのための状態管理ライブラリです。
Vue.jsでは、UIを独立した再利用可能な部品として扱うため、コンポーネントという単位を利用します。
コンポーネント間でのデータのやり取りは可能ですが、アプリケーションが複雑になるにつれて、これらのやり取りを追跡することが難しくなります。これにより、アプリケーションの可読性が低下する可能性があります。Vuexは、このような課題を解決するために開発されました。
Vuexの核心は「単一の真実の源(Single Source of Truth)」という考え方です。アプリケーション全体の状態を一箇所(ストア)で集中管理し、状態の変更は予測可能で追跡可能な専用の仕組みを通じてのみ行います。これにより、複雑なアプリケーションでもデータフローを明確に保ち、デバッグが容易になります。
このアプローチを採用するライブラリは「状態管理ライブラリ」と呼ばれ、Vuexの他にも以下のようなライブラリがあります。
- Redux(React)https://redux.js.org/
- React context(React)https://ja.react.dev/learn/passing-data-deeply-with-context
- Zustand(React)https://zustand-demo.pmnd.rs/
- Pinia(Vue.js)https://pinia.vuejs.org/
【補足】Vue.jsとVuexの誕生
2014年:Vue.jsがEvan Youによって開発されました。Evan YouはAngular JSの開発にも関わっていました。
2015年:Vuexが登場しました。Reduxに触発されており、Vue.jsアプリケーションの状態管理を簡素化することを目的としています。
コンポーネント間でのデータの受け渡し
Vuexを使うとなぜ嬉しいのでしょうか。
例として、以下のようなコンポーネント構造があり、最上層の Component と最下層の GreatGrandChildComponent との間でデータの参照と更新を行いたい場合を考えます。
Vuexを使わない場合と使う場合で、どのような実装になるのかをイメージと合わせて見ていきます。
Vuexを使わない場合
→ 各コンポーネント間でpropsを用いて、データの受け渡しをする。
それぞれのコンポーネントで、指定したい文字サイズの入ったprops
を各階層へバケツリレーのように伝達していきます。この時、間にある ChildComponent や GrandChildComponent にも、props
を受け取り管理するためのコードを書かなければいけません。
Vuexを使う場合
→ データを一元管理して、必要なコンポーネントでのみデータを参照・更新する。
「ストア」というデータを一元管理する領域を用意します。
Component からストアに対してデータの更新を行い、 GreatGrandChildComponent では、ストアからデータを参照することで、各コンポーネントから無駄なprops
の管理は不要なので、少ないコードで実装することができて可読性も向上します。
Vuexの使用を検討すべき場合
- アプリケーションが大規模になり、多くのコンポーネント間でデータを共有する必要がある場合
- 複数のコンポーネントで同じ状態を共有する場合 など
私が参画したプロジェクトでは、多数の画面で顧客情報やユーザー独自の設定値などを共有する必要があるため、コードの可読性・保守性・拡張性の向上などを目的にVuexを導入しています。
ストアを用いてデータ操作を行うための4つのオプション
App.vueなどで新しいストアインスタンスを生成して、以下4つのオプションを用いて各コンポーネントからストアに格納されているデータを参照・更新します。
- state
- グローバルな変数
- getters
- stateを参照する関数
-
getters
で呼び出す
- mutations
- stateを更新する関数
-
commit
で呼び出す
- actions
- mutation経由でstateを更新する関数
-
dispatch
で呼び出す
ベストプラクティス
公式サイトでは以下の図が掲載されています。Vuexにおけるベストプラクティスとして、コンポーネントから直接データを更新(mutations
の直接呼び出し)せず、actions
を経由することが推奨されています。actions
にはデータ更新のルール(バリデーションチェックや非同期処理など)を記述し、actions
がmutations
を呼び出してデータを更新します。
このアーキテクチャにより、以下のメリットが生まれます:
- データフローの一方向性が保たれ、予測可能性が向上する
- 意図しないデータの変更を防止できる
- デバッグが容易になる(Vuex DevToolsでの追跡が可能)
具体的な使用方法
ここからは、実際に私のプロジェクトで使用しているVuexのコードについて、ストアの4つのオプションの記述例に焦点を当ててご紹介します。
ストアの作成
src/store/index.js
でストアを作成します。ストアはアプリケーションに唯一のものですが、可読性向上のためにモジュールに分割することもできます。
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
// ストアを作成する
export default new Vuex.Store({
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... },
modules: { ... },
});
state・getters・mutationsの例
src/store/mis-modules/customList.js
では、リストの管理にVuexを利用しています。
import * as types from '../mutationsTypes';
// state: アプリケーションの状態を保持するオブジェクト
// この例ではカスタムリストの配列を管理している
const stateCustomList = {
listCustomList: [], // カスタムリストを格納する配列
};
// getters
const getters = {
getListCustomList: state => {
return state.listCustomList; // stateを返すだけ
}
};
// acctions
const actions = {
setListCustomList({ commit }, list) {
commit(types.SET_LIST_CUSTOM_LIST, list); // mutationsを呼び出す
}
};
// mutations
const mutations = {
[types.SET_LIST_CUSTOM_LIST](state, list) {
if (Array.isArray(list)) {
state.listCustomList = list; // stateを更新する
}
}
};
export default {
namespaced: true,
state : stateCustomList,
getters,
actions,
mutations
};
stateの呼び出し
this.$store.state.{state名}
でstateを取得することができます。computed内でmapState
ヘルパーを使用することで、複数のstateを簡単な記述で取得することもできます。
computed: {
...mapState({
positionList: state => state.mis.position.positionList,
authorityInfo: state => state.mis.users.userData.AuthorityInfo,
...
}),
}
getterの呼び出し
this.$store.getters.{getter名}
でgetterを呼び出すことができます。computed内でmapGetters
ヘルパーを使用することで、複数のgetterを簡単な記述で呼び出すこともできます。
computed: {
...mapGetters({
// `this.listCustomList`を`this.$store.getters.getListCustomList`にマッピングさせている
listCustomList: 'mis/customList/getListCustomList',
...
}),
}
actionsの呼び出し
this.$store.dispatch('action名')
でactionを呼び出すことができます。methods内でmapActions
ヘルパーを使用することで、複数のactionsを簡単な記述でマッピングすることができます。
methods: {
...mapActions({
// `this.setListCustomList()` を `this.$store.dispatch('setListCustomList')`にマッピングする
setListCustomList: 'mis/customList/setListCustomList',
setPositionList: 'mis/position/setPositionList',
...
}),
まとめ
Vuexを導入することで得られる主なメリット:
- コンポーネント間の
$emit
・props
によるデータのバケツリレーが不要になる - アプリケーションの状態管理が一元化され、可読性が向上する
- 状態変更の追跡が容易になり、データ操作の正確性と予測可能性が高まる
- 大規模アプリケーションでの開発効率と保守性が向上する
注意点:
Vuexは強力なツールですが、小規模なアプリケーションでは過剰な場合もあります。無闇にすべての変数をストアで管理するのではなく、以下の点に注意する必要がありそうです。
- 複数の離れた階層のコンポーネント間でデータ共有が必要か
- アプリケーションの規模と複雑さ
- チームの開発スタイルと学習コスト
適切なケースでVuexを活用することで、Vue.jsアプリケーションの品質と開発効率を大きく向上させることができると感じました。
ここまで読んでいただき、ありがとうございました。記載内容に誤り・追記がありましたらお気軽にコメントいただきますと大変助かります。
株式会社アドバンテッジリスクマネジメント(ARM)では、一緒に働く仲間を募集しています!
興味を持っていただけた方は、ぜひ以下のリンクから詳細をご覧ください
Discussion