Vue2向け状態管理ライブラリVuexの基本と実践的使い方

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 })
})
  

タグ: vue.js Vuex 状態管理 ストア ミューテーション

7月7日 16:22 投稿