yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

Vuexの勉強をしてみる

vue.js入門の7章のタスク管理アプリケーションを写経しながらVuexの勉強をしてみる

  • App.vueのtemplate部分

ここはVuexを使わない場合と何も変わらない。ディレクティブでVueインスタンスに指示を送っているだけ。

<template>
  <div id="app">
    <h2>タスク一覧</h2>
    <ul>
      <li v-for="task in tasks" v-bind:key="task.id">
        <input type="checkbox" v-bind:checked="task.done"
          v-on:change="toggleTaskStatus(task)">
        {{ task.name }}
        -
        <span v-for="id in task.labelIds" v-bind:key="id">
          {{ getLabelText(id) }}
        </span>
      </li>
    </ul>
    <form v-on:submit.prevent="addTask">
      <input type="text" v-model="newTaskName" placeholder="新しいタスク">
    </form>

    <h2>ラベル一覧</h2>
    <ul>
      <li v-for="label in labels" v-bind:key="label.id">
        <input type="checkbox" v-bind:value="label.id" v-model="newTaskLabelIds">
        {{ label.text }}
      </li>
    </ul>
    <form v-on:submit.prevent="addLabel">
      <input type="text" v-model="newLabelText" placeholder="新しいラベル">
    </form>

    <h2>ラベルでフィルタ</h2>
    <ul>
      <li v-for="label in labels" v-bind:key="label.id">
         <input type="radio" v-bind:checked="label.id === filter"
          v-on:change="changeFilter(label.id)">
          {{ label.text }}
      </li>
      <li>
        <input type="radio" v-bind:checked="filter == null" v-on:chenge="changeFilter(null)">
        フィルタしない
      </li>
    </ul>

    <h2>保存と復元</h2>
    <button type="button" v-on:click="save">保存</button>
    <button type="button" v-on:click="restore">復元</button>
  </div>
</template>
  • App.vueのscriptタグ内

script部分のcomputed、method部分でVuexのstoreに対するデータの取得、更新処理を書いている。更新部分にcommitとdispatchの2パターンがあるのがややこしいけど

  • ミューテーション(通常のstate更新)を行う際はcommit
  • アクション(非同期処理や外部APIとのやり取り)を行う際はdispatch

と分けて使う。ゲッター(stateの取得処理)とミューテーションを基本的には使ってカバーできないものをアクションで実装するらしい。

<script>
export default {
  data () {
    return {
      newTaskName: '',
      newTaskLabelIds: [],
      newLabelText: ''
    }
  },
  computed: {
    tasks () {
      return this.$store.getters.filteredTasks
    },
    labels () {
      return this.$store.state.labels
    },
    filter () {
      return this.$store.state.filter
    }
  },
  methods: {
    addTask () {
      this.$store.commit('addTask',  {
        name: this.newTaskName,
        labelIds: this.newTaskLabelIds
      })
      this.newTaskName = ''
      this.newTaskLabelIds = []
    },

    toggleTaskStatus (task) {
      this.$store.commit('toggleTaskStatus', {
        id: task.id
      })
    },

    addLabel () {
      this.$store.commit('addLabel', {
        text: this.newLabelText
      })
      this.newLabelText = ''
    },

    getLabelText(id) {
      const label = this.labels.filter(label => label.id === id)[0]
      return label? label.text : ''
    },

    changeFilter (labelId) {
      this.$store.commit('changeFilter', {
        filter: labelId
      })
    },

    save () {
      this.$store.dispatch('save')
    },

    restore () {
      this.$store.dispatch('restore')
    }
  }
}
</script>
  • Vuexのstore部分

storeはstate(アプリケーションの状態)を管理する部分でこれがVuexの根幹。

Vueインスタンスにdataやmethods、computedなどのoptionを書いていくのと同じような感じでstate(Vueインスタンスのdataのようなもの)と上記のgetters、mutations、actionsを書いていく。

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

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    tasks: [
      {
        id: 1,
        name: '牛乳を買う',
        labelIds: [1, 2],
        done: false
      },
      {
        id: 2,
        name: 'Vue.jsの本を買う',
        labelIds: [1, 3],
        done: true
      }
    ],
    labels: [
      {
        id: 1,
        text: '買い物'
      },
      {
        id: 2,
        text: '食料'
      },
      {
        id: 3,
        text: '本'
      }
    ],
    nextTaskId: 3,
    nextLabelId: 4,

    filter: null
  },
  getters: {
    filteredTasks (state) {
      if(!state.filter) {
        return state.tasks
      }

      return state.tasks.filter(task => {
        return task.labelIds.indexOf(state.filter) >= 0
      })
    }
  },
  mutations: {
    addTask (state, { name, labelIds }) {
      state.tasks.push({
        id: state.nextTaskId,
        name,
        labelIds,
        done: false
      })
      state.nextTaskId++
    },

    toggleTaskStatus (state, { id }) {
      const filtered = state.tasks.filter(task => {
        return task.id === id
      })

      filtered.forEach(task => {
        task.done = !task.done
      })
    },

    addLabel (state, { text }) {
      state.labels.push({
        id: state.nextLabelId,
        text
      })
      state.nextLabelId++
    },

    changeFilter (state, { filter }) {
      state.filter = filter
    },

    restore(state, { tasks, labels, nextTaskId, nextLabelId })
    {
      state.tasks = tasks
      state.labels = labels
      state.nextTaskId = nextTaskId
      state.nextLabelId = nextLabelId
    }
  },

  actions: {
    save ({ state }) {
      const data = {
        tasks: state.tasks,
        labels: state.labels,
        nextTaskId: state.nextTaskId,
        nextLabelId: state.nextLabelId
      }
      localStorage.setItem('task-app-data', JSON.stringify(data))
    },

    restore ({ commit }) {
      const data = localStorage.getItem('task-app-data')
      if (data) {
        commit('restore', JSON.parse(data))
      }
    }
  }
})

export default store

あとはstoreのモジュール分割やRouterとの連携などもVue.js入門のこの後のページで記載されているが、Vuexの基本的なところは

  • Vueインスタンスのdataで保持していた部分をstoreのstateに切り出してgetter、computed、actionで使う。
  • 使いどころはユーザ情報などのコンポーネントを跨いだグローバルなデータ

ということが分かっていればとりあえず良いかな?