君が代

基本的にワイの忘備録

【Vue忘備録】Vuexの巻

Vuexとは

Vuexとはアプリで扱うデータを集中的に管理するためのライブラリ。

メリット - アプリに点在するデータを一元的なストアで管理できる - コンポーネント階層にかかわらずストアを直接参照できるので、データの受け渡しコードが減少する - ストア上のデータはリアクティブなので、コンポーネントとも自動で同期される - データの更新フローが一貫するので、コードの見通しが改善する

ある程度の規模以上で、中長期的に運用していくのであれば、最初からVuexの利用をした方が良い。

Vuexの基本

store.js

// Vuexを有効化する
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// ストアを作成する
export default new Vuex.Store({
// 厳密モードを有効にする
  strict: true,
// ストアにデータの初期値を定義する
  state: {
    count: 0
  },
// ステート操作のためのメソッドを定義する
  mutations: {
    minus(state) {
      state.count--
    },
    plus(state) {
      state.count++
    }
  },
  actions: {
  }
})

ストアを表すのがVue.Storeオブジェクト。Vue.Storeオブジェクトの中でも、特にデータ本体を表すのがstateオプション。そのステートの操作は常にメソッド経由で行うのがお作法。ステート操作のメソッドをミューテーション(mutation)と呼び、ミューテーションに操作を限定することで全ての状態変化を監視しやすくなる。

Vuex.Storeオブジェクトの1行目にあるstrict: trueによって厳密モードが有効になりミューテーション以外のステート更新を監視して、反したコードを警告する。

main.js

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

Vue.config.productionTip = false

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

import store from './store'でロードした上でnew Vue({ store,にてVueコンストラクターのstoreオプションとして引き渡す。

App.vue

<template>
  <div>
    <input type="button" value="-" v-on:click="minus" />    
    {{count}}
    <input type="button" value="+" v-on:click="plus" />
  </div>
</template>

<script>
import { mapState } from 'vuex'
//import { mapMutations } from 'vuex'

export default {
  name: 'app',
// 現在のカウントを取得
  computed: {
    count() {
      return this.$store.state.count
    }
  },
  methods: {
// [-]ボタンでカウント値をデクリメント
    minus() {
      this.$store.commit('minus')
    },
// [+]ボタンでカウント値をインクリメント
    plus() {
      this.$store.commit('plus')
    }
  }

  //methods: mapMutations([ 'plus', 'minus' ])
}
</script>

Vuexストアを構成する要素

ステートの内容を加工&取得する〜ゲッター

ゲッター(getters)コンポーネントでいうところの算出プロパティとメソッドの中間のような仕組みで、引数は渡せるが、セッターを設置することはできない。

store.js

  getters: {
    booksCount(state) {
      return state.books.length
    },
    getBooksByPrice(state) {
      return price => {
        return state.books.filter(book => book.price < price)
      }
    },

アロー関数では本来の引数(price)を受け取り、その値を元に書籍情報(store.books)の内容を絞り込んでいる。fileterメソッドはJavascript標準のメソッドで条件式を満たす要素だけを取得する。

App.vue

  <hr />
    <p>書籍は全部で{{booksCount}}冊あります。</p>
    <ul v-for="b of getBooksByPrice(2500)" v-bind:key="b.isbn">
      <li>{{b.title}}({{b.price}}円)<br />ISBN:{{b.isbn}}</li>
    </ul>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'

export default {
  name: 'app',
  computed: mapGetters(['booksCount', 'getBooksByPrice']),

ゲッターは算出プロパティに登録するのがお作法。 computed: mapGetters(['booksCount', 'getBooksByPrice'])のコードは下記とほぼ同じ意味。

  computed: {
    booksCount() {
      return this.$store.getters.booksCount;
    }
  },
  methods: {
    getBooksByPrice(price) {
      return this.$store.getters.getBooksByPrice(price)
    }
  }

ストアの状態を操作する〜ミューテーション

ミューテーションが持つ引数をペイロードと呼ぶ。

store.js

mutations: {
    addBook(state, payload) {
      state.books.push(payload.book)
    }

複数の情報を渡せるようにペイロードは「名前:値, .....」のオブジェクト形式で表すのが基本。

App.vue

  methods: {
    onclick() {

      this.$store.commit('addBook', {
        book: {
          isbn: this.isbn, title: this.title, price: this.price
        }
      })

ペイロードはcommitメソッドの第二引数として渡します。

下記のような書き方もできる。

      this.$store.commit({
        type: 'addBook',
        book: {
          isbn: this.isbn, title: this.title, price: this.price
        }
      })

非同期処理を実装する〜アクション

ミューテーションには非同期処理を含んでいけないので、ミューテーションは常に同期処理として表し、非同期処理はアクションとして切り出す。

store.js

actions: {    
    addAsync(context, payload) {
// 500ミリ秒後にミューテーション(ADD_BOOK)をコミット
      setTimeout(function() {
        context.commit(ADD_BOOK, payload)
      }, 5000)
    }

アクションでは、引数としてコンテキストオブジェクト(object)を受け取る。

下記で準備したaddAsyncをコンポーネントから呼び出します。 App.vue

this.$store.dispatch('addAsync', {
        book: {
          isbn: this.isbn, title: this.title, price: this.price
        }
      })

アクションを呼び出すことをディスパッチするといい、dispatchメソッドを利用します。