基礎からメモ: Vue.js CH8-p264 コンポーネントでストアを使う

コンポーネントでストアを使う

親子間コンポーネントで状態管理する例

まずsrc/store.jsを定義

[src/store.js]

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    message: '初期メッセージ'
  },
  getters: {
    message(state) { return state.message }
  },
  mutations: {
    setMessage(state, payload) {
      state.message = payload.message
    }
  },
  actions: {
    doUpdate({ commit }, message) {
      commit('setMessage', { message })
    }
  }
})

export default store

src/main.jsからstore.jsを読み込みアプリケーションにストアを登録

[src/main.js]

import Vue from 'vue'
import App from './App.vue'
import store from '@/store.js'


Vue.config.productionTip = false

new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

App.vueに、ローカルの message とストアの message を同期させる算出プロパティを定義し、message を変更するための子コンポーネントEditFormのカスタムタグを記述

[src/App.vue]

<template>
  <div id="app">
    <p>{{ message }}</p>
    <EditForm/>
  </div>
</template>

<script>
import EditForm from '@/components/EditForm.vue'

export default {
  name: 'App',
  components: {
    EditForm
  },
  computed: {
    message() { return this.$store.getters.message }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

コンポーネントEditForm

[src/components/EditForm.vue]

<template>
  <div class="edit-form">
    <input type="text" ref="input" :value="message" @input="doUpdate">
  </div>
</template>

<script>
export default {
  name: 'EditForm',
  computed: {
    message() { return this.$store.getters.message }
  },
  methods: {
    doUpdate() {
      this.$store.dispatch('doUpdate', this.$refs.input.value )
    }
  }
}
</script>

これで Appコンポーネントに描かれたメッセージと、EditFormコンポーネントで描画されたとインプットフォームへの入力がリアルタイムで同期するようになる。流れをまとめると、

store.jsに登録されたストアは state に messageデータを保持している。

main.jsからそのstore.jsを読み込み、Appコンポーネントを使う

Appコンポーネントは、EditForm子コンポーネントを読み込む

App は computed を使って、ストアのゲッター経由で初期メッセージを描画

EditFormのインプットフォームにも同じ方法で初期メッセージを描画

インプットフォームに何かが入力されると、EditFormコンポーネントの doUpdateメソッド が呼び出され、 doUpdateメソッドはストアの同名メソッドをディスパッチ(機能の呼び出し)をする。そのさいフォーム入力されたデータを渡す。

ストアのdoUpdateメソッドは mutations である setMessageメソッドを使って state の messageデータを書き換えることで、フォームとその上の表示が同期される。

何段階もあって一見複雑だが、メインJSとそこからつながる親子コンポーネント間でストアのデータが共有されていること、結果的にストアのデータはミューテーションを経由してのみ変更されているのがわかる。(ステートはミューテーション以外から変更してはいけないというルール)

ステートやゲッターに v-model を使う

フォームにv-modelを使うと、フォームに紐付けられたデータを自動的に更新するようになる。ステートの場合はミューテーション以外から書き換えてはいけないルールなのでこれはエラーになる。だから先の例ではフォームのv-onからイベントハンドラを呼び出し、そこからディスパッチでミューテーションを呼び出すという方法をとった。

ただし、算出プロパティのセッターからディスパッチを使ってミューテーションを間接的に呼び出すという書き方もできる。これだとv-modelからデータの自動更新がされる際にデータに定義されたセッターが自動的に呼び出されるので、よりシンプルに書ける。

[src/components/EditForm.vue]

<template>
  <div class="edit-form">
    <input type="text" v-model="message">
  </div>
</template>

<script>
export default {
  name: 'EditForm',
  computed: {
    message: {
      get() { return this.$store.getters.message },
      set(value) { this.$store.dispatch('doUpdate', value) }
    }
  }
}
</script>

これだとインプットフォームへの入力は"message"データの内容としてv-modelで紐付けられているので、"message"データのセッターに渡される引数はそのフォームへの入力内容を自動的に表し、this.$refs.input.valueと書かずに単にvalueだけで済む。