【Vuex】stateの変更を監視する3つの方法

現在バージョンアップ作業している所得税・住民税・事業税・国民健康保険計算シミュレーションは、Vuexを使用しています。その際に、stateの変更を監視したいケースが出てきたのでその方法をご紹介します。

今回の想定は、都道府県のセレクトコントロールがあり、その値をVuexのストアで管理しているケースです。画面はこんな感じのコントロール。いたって普通。

Vuexのstoreの中身はこちら。

const store = new Vuex.Store({
  state: {
    prefecture: null
  },
  getters: {
    prefecture(state) { return state.prefecture }
  },
  mutations: {
    setPrefecture(state, payload) {
      state.prefecture = payload.prefecture
    }
  },
  actions: {
    doUpdatePrefecture({ commit }, prefecture) {
      commit('setPrefecture', { prefecture })
    }
  }
})

Vue.jsの場合

比較用でまずはVuexを使用していないデータや算出プロパティを変更監視する方法を。Vue.jsでは以下のようにwatchを使います。楽ちん。

new Vue(
  data() {
    return {
      prefecture: '福岡県'
    }
  },
  watch: {
    prefecture: function(newValue, oldValue) {
      console.log('prefecture changed! %s => %s', oldValue, newValue)
    }
  }
)

Vuexでストアの状態を監視する方法は3つ

ケースによって使い分けができるように、3種類あります。

  • watch
  • subscribe
  • subscribeAction ※ Vuex 2.5.0~

subscribeとsubscribeActionは監視というよりは、イベントフックみたいなものでしょうか。
それでは早速それぞれのケースを見ていきます。

※ VuexがVueのルートコンポーネントにstoreオプションと指定されていることとします。

watch

Vue.jsのwatch(ウォッチャ)と考え方は同じです。stateの変更を直接監視します。

export default {
  …
  mounted() {
    this.$store.watch(
      (state, getters) => getters.prefecture,
      (newValue, oldValue) => {
         console.log('prefecture changed! %s => %s', oldValue, newValue)
      }
    )
  }
}

結果はこちら

prefecture changed! null => 福岡県

prefecture changed! 福岡県 => 東京都

変更前と変更後の値が、Vue.jsのwatchと同じように取得できてます。

subscribe

Vuexのドキュメントには、
ストアへのミューテーションを購読します。handler は、全てのミューテーションの後に呼ばれ、引数として、ミューテーション ディスクリプタとミューテーション後の状態を受け取ります。
とあります。ミューテーションが実行された後なので、値が変わった後に処理を挟むことができるということですね。

購読するミューテーションは全てとなるので、どのミューテーションが呼ばれたかはmutation.typeで判定します。

export default {
  …
  mounted() {
    this.$store.subscribe((mutation, state) => {
      if (mutation.type === 'setPrefecture') {
        console.log('update prefecture! %s', state.prefecture);
      }
    )
  }
}

結果

update prefecture! 福岡県

update prefecture! 東京都

変更前の値が必要ない場合はこちらでもOKですね。

subscribeAction

subscribeはミューテーションの購読ですが、こちらはアクションに対しての購読みたいですね。Vuexのドキュメントには、ストアアクションを購読します。handler はディスパッチされたアクションごとに呼び出され、アクション記述子と現在のストア状態を引数として受け取りますとだけ書かれてます。これもアクション実行後なのかと思いましたが違いました。アクション実行前でした。
ちなみに、Vuex2.5.0で新規追加された機能なのでご注意を。

引数のactionにはアクションコール時の情報が、stateには現在の状態なので変更前と変更後の値が参照可能です。

export default {
  …
  mounted() {
    this.$store.subscribeAction((action, state) => {
      if (action.type === 'doUpdatePrefecture') {
        console.log('Call doUpdatePrefecture action! %s', state.prefecture);
      }
    )
  }
}

結果

Call doUpdatePrefecture action! null => 福岡県

Call doUpdatePrefecture action! 福岡県 => 東京都

Vuex 3.1.0以降

3.1.0からは、アクション実行前と実行後のどちらかを指定できるようになり、さらにコントロールできるようになりました。
デフォルトの動作は’before’ということなので、アクション実行前です。

export default {
  …
  mounted() {
    this.$store.subscribeAction({
      before: (action, state) => {
        if (action.type === 'doUpdatePrefecture') {
          console.log('prefecture changed by action before! state: %s action.payload: ', state.InputItem.prefecture, action.payload)
        }
      },
      after: (action, state) => {
        if (action.type === 'doUpdatePrefecture') {
          console.log('prefecture changed by action after! state: %s action.payload: ', state.InputItem.prefecture, action.payload)
        }
      }
    }
}

結果

prefecture changed by action before! state: null action.payload:  福岡県
prefecture changed by action after! state: 福岡県 action.payload:  福岡県

prefecture changed by action before! state: 福岡県 action.payload:  東京都
prefecture changed by action after! state: 東京都 action.payload:  東京都

アクション実行前と実行後で、stateの中身が更新されているのがわかります。

watch、subscribe、subscribeActionの処理順

3パターン全てを指定した場合の実行順。

  1. subscribeAction.before
  2. subscribe
  3. subscribeAction.after
  4. watch

結果

// 1
prefecture changed by action before! state: null action.payload:  福岡県
// 2
update prefecture! 福岡県
// 3
prefecture changed by action after! state: 福岡県 action.payload:  福岡県
// 4
prefecture changed! null => 福岡県

watchとsubscribe(subscribeAction)の使い分け

  • stateを直接監視したい場合は`watch’を使う

  • ミューテーションやアクションに処理を挟みたい場合はsubscribe(subscribeAction)を使う

当たり前ですが、他のミューテーションで監視しようとしている値を変更した場合、この変更を感知できるのは`watchだけ’です。

const store = new Vuex.Store({
  state: {
    prefecture: null,
    hoge: null
  },
  getters: …
  mutations: {
    setPrefecture(state, payload) {
      state.prefecture = payload.prefecture
    },
    setHoge(state, payload) {
      state.hoge = payload.hoge
      if (hoge == 'fuga') {
        // ここの変更を監視できるのはwatchのみ
        state.prefecture = '福岡県'
      }
    }
  },
  actions: …
})

まとめ

Vuexの初期化時やlocalStorageから値を取得しストアにセットする場合はスルーして、ユーザー入力時にのみ動かしたいとか色々なケースがあると思います。各性質を正しく理解し、監視したい値に対してのような処理を挟みたいのかを考え、3パターンを使い分けるとよさそうですね。