はじめに
Qiita 初投稿です
Vuex について勉強した際のメモをまとめました。
まだWeb開発初心者のため、単語や言い回しなどおかしなところがあるかもしれません。
間違った記載がありましたらご教授いただけると嬉しいです!
Vuex とは
Vue を用いたアプリケーションの開発では、コンポーネント間でのデータのやりとりが頻繁に発生する。
コンポーネント間でデータの整合性を保つためには 各コンポーネントで値渡しの処理を記述する必要があり、ソースの可読性とデバッグ効率が低下しやすい。
Vuex は、Vueアプリケーションにおけるデータの状態管理を一元化して開発効率を上げることを目的としたライブラリである。
Vue アプリケーションで扱うデータセットを Store と呼ばれる領域で一元管理することで、各コンポーネントは Store にアクセスすれば常に共通の値を参照することができるようになる。
また、Store のデータに対する操作を予め定義しておけるので、予期しない操作の防止や保守性・可読性の向上が見込める。
Vuex / Store の定義
Vuex を Vue アプリケーションで使用する際は以下のように宣言し、Store を定義する。
"use strict"
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// Storeを生成
const store = new Vuex.Store({
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
});
export default store;
import Vue from "vue";
import store from "store";
// Vueインスタンスの定義時に、Store情報を組み込む
new Vue({
el: '#app',
store,
render: h => h(App)
});
store
の宣言で state
, getters
, mutations
, actions
という項目があるが、これらは Store が保持するデータ項目や、Store 上のデータを外部(コンポーネント等)から操作するための関数を定義する項目である。
state
, getters
, mutations
, actions
Store の作成時に定義できる項目は下記の4つである。
項目名 | 概要 |
---|---|
state | Store で管理するデータ項目の定義 |
getters | state 内のデータの状態から算出される値(≒算出プロパティ) |
mutations | state のデータを直接操作するための関数(非同期処理は定義不可) |
actions | mutations の操作を各コンポーネントから呼び出すために使用する関数(非同期処理を定義可) |
↓ 定義のイメージ
const store new Vuex.Store({
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
});
それぞれの項目は用途によって使い分けされるので、順番に説明する。
state
( Store で管理するデータ項目の定義 )
Vuex の Store で管理するデータ項目を定義する。
ここに定義したデータは Vueアプリケーション内の各コンポーネントから適宜取得・更新することができる。
state 定義の例
以下の例では、データ項目 count
を初期値0で定義している。
// Store 定義
const store = new Vuex.Store({
state: {
count: 0
},
});
export default store;
また、次のように store
から state
を切り離して定義することもできる。
const state = {
count: 0
};
// Store 定義
const store = new Vuex.Store({
state,
});
export default store;
コンポーネントから state の値を使用
コンポーネントからは、 this.$store.state
を経由して値を参照できる。
参照する際は computed
を用いることで、その変更を監視できる。
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
name: "Counter",
computed: {
count () {
return this.$store.state.count
}
}
};
</script>
コンポーネントから呼び出す際は mapState
ヘルパーを使用することもできる。
複数の state
の項目を利用する際はこちらの方が簡潔に書ける。
// Store 定義
const store = new Vuex.Store({
state: {
count1: 0,
count2: 0
},
});
export default store;
<template>
<div>
<div>{{ count1 }}</div>
<div>{{ count2 }}</div>
</div>
</template>
<script>
import { mapState } from "vuex"
export default {
name: "Counter",
computed: {
...mapState([
"count1", // 注意)プロパティ名は ' または " でくくる必要がある
"count2"
])
}
};
</script>
state データの更新・削除について
state
は this.$store.state.count = 100
のように__直接更新・削除を行なってはいけない__
基本的に Store 内のデータに対する操作は、後述する mutations に定義する。
getters
( state 内のデータの状態から算出される値(≒算出プロパティ))
getters
では state
のデータに対する算出プロパティを定義し、各コンポーネントで利用できる。
例えば、TODOリストの未完了のデータ数を取得する関数 doneTodoCount
が、以下のように定義されるとする。
<template>
<div>{{ doneTodoCount }}</div>
</template>
<script>
export default {
name: "TodoList",
computed: {
doneTodoCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
};
</script>
上記の書き方で目的は果たせるが、他のコンポーネントでこの関数を利用したい場合にはこの関数をコピーするか、共通処理として外部モジュールに切り出してインポートする必要がある。
getters
を使用することで、Store 経由で共通の算出プロパティとして使用できるようになる。
getters の定義
getters
に定義する関数は第1引数に state
をもち、ここから Store のデータにアクセスできる。
// Store 定義
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, label: '...', done: true },
{ id: 2, label: '...', done: false }
]
},
getters: {
// 第1引数に state をもつ
doneTodoCount: (state) => {
return state.todos.filter(todo => todo.done).length
}
}
// ...
});
export default store;
コンポーネントから getters の関数を使用
state
と同様の形で this.$store.getters
に含まれるゲッター関数を computed
で監視する。
<template>
<div>{{ doneTodoCount }}</div>
</template>
<script>
export default {
name: "TodoList",
computed: {
doneTodoCount () {
return this.$store.getters.doneTodoCount
}
}
};
</script>
mapGetters
ヘルパー関数によって参照することもできる。
<template>
<div>{{ doneTodoCount }}</div>
</template>
<script>
import { mapGetters } from "vuex"
export default {
name: "TodoList",
computed: {
...mapGetters([
"doneTodoCount"
])
}
};
</script>
※getters では同期的な処理のみを記述する。Ajax等の非同期処理を実行したい場合は、後述する actions で定義する。
mutations
( state のデータを直接操作する関数 )
記述中
actions
( mutations の操作 + 非同期処理する関数 )
記述中
モジュール分割による store の切り分け
Store は以下のようにモジュール分割して定義することもできる。
const moduleA = {
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
};
export default moduleA;
const moduleB = {
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
};
export default moduleB;
import Vue from "vue";
import Vuex from "vuex";
import moduleA from "./moduleA.js";
import moduleB from "./moduleB.js";
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
moduleA,
moduleB
}
});
export default store;
モジュール分割することで Store が肥大化することを防ぎ、またカテゴリ等によって Store を分けて管理できる。
デフォルトでは各モジュールで宣言した getters
, mutations
, actions
はグローバル名前空間に登録されるため、複数のモジュールが同じミューテーション/アクションタイプに反応することになる。
名前空間をモジュール単位で登録したい場合は、モジュールの宣言時に namespaced: true
を設定する。
const moduleA = {
namespaced: true,
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... }
};
モジュール分割した Store をコンポーネントから参照する
以下のようなモジュール分割された Store を定義し、コンポーネントから参照してみる。
const moduleA = {
namespaced: true,
state: {
result: undefined
},
mutations: {
setResult(state, data) {
state.result = data
},
clearResult(state) {
state.result = undefined
}
},
actions: {
setResult({ commit }, data) {
commit("setResult", data)
},
clearResult({ commit }) {
commit("clearResult")
}
}
};
export default moduleA;
const moduleB = {
// 以下、moduleA と同じ内容
namespaced: true,
state: {
result: undefined
},
mutations: {
setResult(state, data) {
state.result = data
},
clearResult(state) {
state.result = undefined
}
},
actions: {
setResult({ commit }, data) {
commit("setResult", data)
},
clearResult({ commit }) {
commit("clearResult")
}
}
};
export default moduleB;
moduleA
、moduleB
はそれぞれが
- データ項目
result
- データ項目
result
を直接編集するミューテーションsetResult
、clearResult
- ミューテーションを呼び出すためのアクション
setResult
、clearResult
を持つ。
以下は moduleA
、moduleB
の state項目 result
に対して表示・更新・クリアを実行するコンポーネントである。
<template>
<div class="sample">
<div class="view">
<p>state.result の状態</p>
<div>ModuleA : {{ result_A }}</div>
<div>ModuleB : {{ result_B }}</div>
</div>
<div>
<p>ModuleA の state 操作</p>
<input type="text" v-model="input_A">
<button @click="setResult_A">更新</button>
<button @click="clearResult_A">クリア</button>
</div>
<div>
<p>ModuleB の state 操作</p>
<input type="text" v-model="input_B">
<button @click="setResult_B">更新</button>
<button @click="clearResult_B">クリア</button>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex"
export default {
name: "Sample",
data: function() {
return {
input_A: this.result_A,
input_B: this.result_B
}
},
computed: {
// mapState の第1引数に名前空間(moduleA, moduleB)を指定し、
// それぞれの "state.result" を別名(result_A, result_B)で取得
...mapState(
"moduleA",
{ result_A: state => state.result }
),
...mapState(
"moduleB",
{ result_B: state => state.result }
)
},
methods: {
// mapActions の第1引数に名前空間(moduleA, moduleB)を指定し、
// それぞれのアクションを別名で取得
...mapActions(
"moduleA",
{
setResult_A(dispatch) { dispatch("setResult", this.input_A) },
clearResult_A: "clearResult"
}
),
...mapActions(
"moduleB",
{
setResult_B(dispatch) { dispatch("setResult", this.input_B) },
clearResult_B: "clearResult"
}
)
}
};
</script>
<style scoped>
.sample {
max-width: 500px;
text-align: left;
}
.view {
border: 1px solid black;
padding: 0 0 10px 10px;
}
</style>
上記の ...mapActions("moduleA", [ ... ])
のように、ヘルパー関数の第1引数に store/index.js
で定義した名前空間(moduleA, moduleB)を指定することで、指定したモジュールごとの store を操作できる。
まとめ
Vuex を使うと状態管理がだいぶ楽になることが分かりました。
また、データの扱いがある程度ルール化されているので、初心者にはありがたいです。
最後までご覧いただきありがとうございました!