JavaScript
vue.js
Vuex
Vue.js #4Day 17

改めて学び直すVuex

まえがき

以前 Vue.js用のFluxライクなライブラリVuexを試してみる という記事を書きました。2年前の記事で更新もしていなかったので改めて整理して学び直してみようと思います。今回使用するバージョンは 3.0.1 です。

Vuexとは?

VuexはVue.js用の状態管理ライブラリです。Fluxの影響を受けています。ReactだとReduxに当たります。

Fluxの考え方はFacebookのリポジトリにあるflux-conceptsが詳しいです。その中の図をお借りすると以下のような考え方になります。

flux.png1

Action → Dispatcher → Store → View のようにデータが流れていきます。Vuexでもこの考え方と同じようなデータの流れになります。

vuex.png2

Vuexでのデータの流れは Actions → Mutations → State → Vue Components となります。それぞれの名前が違うだけでFluxの考え方を素直に取り入れている感じがします。

FluxはコンセプトなのでAPIとの通信は特に指定されていませんが、Vuexはその実装なのでベストプラクティスがあります。図を見ると一目瞭然ですが、ActionsでAPIを叩くように図では指示されています(とはいえFluxの流れをくんでいれば大体Action相当の場所でAPI通信を行うのではないでしょうか)。MutationsではDevtoolsとやりとりを行います。

VuexはVue.jsを使う際Fluxでデータ管理を行いたいとき一番使いやすいライブラリといえるでしょう。Vuexを使用するには、Vue.jsの他のプラグインと同様にVue.jsでインスタンスを作る前に use メソッドで登録します。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

簡単に使えるのがうれしいところです。

ストア

Vuexで状態管理を行う場合、まずはじめに作るのはストアです。カウントアップするアプリケーションを例にすると以下のように作成していきます。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    countWithSuffix(state) {
      return `${state.count} 回`
    }
  },
  mutations: {
    increment(state) {
      state.count++
    }
  },
  actions: {
    increment(context) {
      context.commit('increment')
    }
  }
})

const app = new Vue({
  el: '#app',
  computed: {
    showCounter() {
      return this.$store.getters.countWithSuffix
    }
  },
  store,
  methods: {
    increment() {
      this.$store.dispatch('increment')
    }
  }
})
<h1>Counter</h1>
<div id="app">
  <div>
    {{ showCounter }}
  </div>
  <button @click="increment">
    increment
  </button>
</div>

image.png

ストア内には state getters mutations actions のプロパティを持ちます。 state には状態を、 getters には state 内の状態を元に算出した値を返す関数を、 mutations には state のデータを更新する関数を、 actions にはミューテーションにデータをコミットする関数を入れます。

それぞれ見ていきます。

state

state にはデータを格納していきます。多くのFluxライクなライブラリと同様に、Vuexも単一のstateに全ての情報を詰め込んでいきます。

state の参照は getters の関数で行っています。

getters: {
  countWithSuffix(state) {
    return `${state.count} 回`
  }
},

getters に登録した関数の第一引数に state が渡されるのでそこから参照します。

state の更新は mutations の関数で行っています。

mutations: {
  increment(state) {
    state.count++
  }
},

mutations に登録した関数でも第一引数に state が渡されるのでそこから値を更新しています。

このように state は他の関数に第一引数として渡されて参照と更新を行っていきます。

getters

getters は主に state のデータを加工して表示したいときに使用します。今回は count の数値に「回」を後ろに付加するデコレータのような役割で使用していますが、ToDoリストなどを作るときに、DONEかそうでないかでフィルタした結果を渡すときなどにも使えます。

getters に登録した関数の第二引数に getters に登録した他のゲッター関数が渡されます。

getters: {
  countWithSuffix(state, getters) {
    return `${getters.countWithPrefix} 回`
  },
  countWithPrefix(state) {
    return `第 ${state.count}`
  }
},

このように他のゲッター関数を組み合わせて使用することも可能です。

mutations

mutationsstate を更新する関数を登録します。 mutations 内の関数の呼び出しとして特徴的なことはストアの commit 関数を用いて発火させるということです。先に挙げた例では actions 内の関数で commit を実行していました。

actions: {
  increment(context) {
    context.commit('increment')
  }
}

commit 関数は呼び出す関数の名前(タイプという)を第一引数に入れ、第二引数に該当ミューテーションで使用する値(ペイロードという)を入れることが出来ます。

mutations: {
  increment(state, num) {
    state.count += num
  }
},
actions: {
  increment(context) {
    context.commit('increment', 1)
  }
}

受け取るミューテーション側の関数も第二引数に値が入ります。この値はオブジェクトを送ることも当然可能です。

さらにもう一つの commit の使用の仕方としてオブジェクトをそのまま渡すスタイルもあります。

mutations: {
  increment(state, payload) {
    state.count += payload.num
  }
},
actions: {
  increment(context) {
    context.commit({
      type: 'increment',
      num: 1
    })
  }
}

type には実行する関数名を書き、他のプロパティは渡すデータを登録します。これもミューテーションの関数の第二引数に渡されます。

actions

actions はミューテーションをコミットする関数を登録します。

actions: {
  increment(context) {
    context.commit({
      type: 'increment',
      num: 1
    })
  }
}

引数として渡される context オブジェクトは、そこを経由して storegetters も参照することが可能です。ミューテーションの関数を実行するためには commit 関数を実行します。 commit 関数に渡す引数は mutations の節で書いたとおりです。

actions の関数を実行するには dispatch 関数を Vue のインスタンスで実行します。

methods: {
  increment() {
    this.$store.dispatch('increment')
  }
}

Vue のインスタンス側からストアを参照し、ストアの dispactch 関数を実行します。 dispatch 関数も commit 関数と同じように実行する関数の名前を指定して実行します。

methods: {
  increment() {
    this.$store.dispatch({
      type: 'increment',
      num: 2
    })
  }
}

渡されたデータはアクションの関数の第二引数に渡されます。

actions: {
  increment(context, payload) {
    context.commit({
      type: 'increment',
      num: payload.num
    })
  }
}

このようにしてデータを アクション → ミューテーション → ステート と渡していくことが出来ます。

また、actions 内の関数では非同期処理を行うことが可能です。主にアクションの関数内で外部APIの実行し、値を取得しミューテーションに渡してステートを更新していきます。

まとめ

Vuexの基本的な関数と、それぞれの役割についてカウンターアプリを通して学び直してみました。他にもストアオブジェクトを分割するモジュールという便利な機能があったりします。

今回例で作成したカウンターアプリは以下のURLからコードを見ることが出来ます。実行してみたり改造してみたりしてみてください。

https://jsfiddle.net/tomato360/oxo72sqv/5/

VuexはVueと同様に日本語のドキュメントが充実しています。

https://vuex.vuejs.org/ja/

もしさらに興味を持った場合はこちらをよむと良いと思います。