Vuexとは?
VuexはVue.jsアプリケーションのための状態管理パターンおよびライブラリです。集中型ストレージを使用してアプリケーションの全コンポーネントの状態を管理し、対応するルールによって状態が予測可能な方法で変化することを保証します。
基本例
シンプルなVueカウンターアプリケーションから始めましょう:
const Counter = {
// 状態
data() {
return {
count: 0
}
},
// ビュー
template: `
<div>{{ count }}</div>
`,
// 操作
methods: {
increment() {
this.count++
}
}
}
createApp(Counter).mount('#app')
この状態自管理アプリケーションは、以下の要素で構成されています:
- 状態 - アプリケーションのデータソース
- ビュー - 宣言的に状態をビューにマッピング
- 操作 - ビュー上のユーザー入力に応じた状態変化
複数コンポーネント間での状態共有の課題
単一データフローのシンプルさは、複数のコンポーネントで状態を共有する場合に破られやすくなります:
- 複数のビューが同じ状態に依存する
- 異なるビューからの操作が同じ状態を変更する必要がある
これらの問題に対処するため、Vuexはコンポーネントの共有状態をグローバルシングルトンとして管理するアプローチを提供します。
Vuexの使用場面
Vuexは共有状態の管理に役立ちますが、追加の概念とフレームワークを伴います。短期的な便益と長期的な便益を慎重に検討する必要があります。大規模な単一ページアプリケーションを開発しないのであれば、Vuexの使用は冗長に感じられるかもしれません。シンプルなアプリケーションの場合、シンプルなストアパターンで十分です。しかし、中規模から大規模な単一ページアプリケーションを構築する必要がある場合、コンポーネント外部での状態管理は重要な考慮事項となり、Vuexは自然な選択肢となります。
インストール
npm install vuex@3
状態管理の基本
シンプルな状態管理
Vueアプリケーションで反応的なdataオブジェクトの実際のソースとして、コンポーネントインスタンスは単純にプロキシとして機能します。複数のインスタンス間で共有される必要がある状態がある場合、reactiveメソッドを使用してオブジェクトを反応的にすることができます。
const { createApp, reactive } = Vue
const sourceOfTruth = reactive({
message: 'Hello'
})
const appA = createApp({
data() {
return sourceOfTruth
}
}).mount('#app-a')
const appB = createApp({
data() {
return sourceOfTruth
}
}).mount('#app-b')
ストアパターン
デバッグを容易にし、状態変更の追跡を可能にするため、シンプルなストアパターンを実装できます:
const store = {
debug: true,
state: reactive({
message: 'Hello!'
}),
setMessageAction(newValue) {
if (this.debug) {
console.log('setMessageAction triggered with', newValue)
}
this.state.message = newValue
},
clearMessageAction() {
if (this.debug) {
console.log('clearMessageAction triggered')
}
this.state.message = ''
}
}
Vuexの基本概念
ストアの作成
Vuexアプリケーションの中心となるのがストア(倉庫)です。ストアは基本的にはコンテナであり、アプリケーションの大部分の状態を含みます。
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
}
})
State(状態)
Vuexは単一状態ツリーを使用します - つまり、1つのオブジェクトがアプリケーションの全レイヤーの状態を含みます。これにより「単一データソース(SSOT)」として機能します。
コンポーネントでの状態の取得
コンポーネントでVuexの状態を取得する最も簡単な方法は、計算プロパティ内で状態を返すことです:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return this.$store.state.count
}
}
}
mapStateヘルパー
複数の状態を必要とするコンポーネントでは、mapStateヘルパーを使用するとコードが簡潔になります:
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
count: state => state.count,
countAlias: 'count',
countPlusLocalState(state) {
return state.count + this.localCount
}
})
}
}
Getter(ゲッター)
Getterはストアの計算プロパティのようなもので、stateから派生した状態を定義できます:
const store = createStore({
state: {
todos: [
{ id: 1, text: 'タスク1', done: true },
{ id: 2, text: 'タスク2', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
getTodoById: state => id => {
return state.todos.find(todo => todo.id === id)
}
}
})
mapGettersヘルパー
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'doneTodosCount',
'anotherGetter'
]),
...mapGetters({
doneCount: 'doneTodosCount'
})
}
}
Mutation(ミューテーション)
Vuexのストア内の状態を変更する唯一の方法はミューテーションをコミットすることです。ミューテーションはイベントに似ており、文字列のイベントタイプとコールバック関数を持ちます:
const store = createStore({
state: {
count: 1
},
mutations: {
increment(state) {
state.count++
},
incrementBy(state, payload) {
state.count += payload.amount
}
}
})
// ミューテーションの呼び出し
store.commit('increment')
store.commit('increment', { amount: 10 })
重要なのは、ミューテーションは同期関数でなければならないということです。非同期操作を含む場合、状態の変更を追跡することが困難になります。
mapMutationsヘルパー
import { mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([
'increment',
'incrementBy'
]),
...mapMutations({
add: 'increment'
})
}
}
Action(アクション)
Actionはミューテーションに似ていますが、2つの重要な違いがあります:
- Actionはミューテーションをコミットしますが、直接状態を変更しません
- Actionは任意の非同期操作を含むことができます
const store = createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment({ commit }) {
commit('increment')
},
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
})
// アクションのディスパッチ
store.dispatch('increment')
store.dispatch('incrementAsync', { amount: 10 })
アクションの組み合わせ
Actionは非同期であるため、複数のActionを組み合わせることで複雑な非同期フローを処理できます:
actions: {
async actionA({ commit }) {
commit('gotData', await getData())
},
async actionB({ dispatch, commit }) {
await dispatch('actionA')
commit('gotOtherData', await getOtherData())
}
}
mapActionsヘルパー
import { mapActions } from 'vuex'
export default {
methods: {
...mapActions([
'increment',
'incrementBy'
]),
...mapActions({
add: 'increment'
})
}
}
Module(モジュール)
アプリケーションが複雑になると、ストアオブジェクトが肥大化する可能性があります。この問題を解決するため、Vuexはストアをモジュールに分割することを許可しています:
const moduleA = {
state: () => ({ count: 0 }),
mutations: {
increment(state) {
state.count++
}
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
}
const moduleB = {
state: () => ({ count: 0 }),
mutations: {
decrement(state) {
state.count--
}
}
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
// モジュールの状態にアクセス
store.state.a.count // -> moduleAの状態
store.state.b.count // -> moduleBの状態
モジュールの動的登録
// ストア作成後のモジュール登録
store.registerModule('myModule', {
state: () => ({ count: 0 }),
mutations: {
increment(state) {
state.count++
}
}
})
// ネストされたモジュールの登録
store.registerModule(['nested', 'myModule'], {
state: () => ({ count: 0 })
})