【Vue.js】vuexを使ってデータを管理する

はじめに

状態管理ライブラリのvuexを使ってデータを管理します。

vuexの設定

まずはvuexをインストールします。

$ yarn add vuex

vuexの設定ファイルを作成します。

$ touch app/javascript/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from '../plugins/axios'
// axiosを使ってapi通信でデータを取ってくるのでaxiosをインポートします

Vue.use(Vuex)
// モジュールシステムを使う際に必須

export default new Vuex.Store({
  state: {  //  stateで空のtasksを定義
    tasks: []
  },

  getters: {   // getterでstateのtasksにアクセスできる
    tasks: state => state.tasks
  },

  mutations: {   // mutationsでstateの状態を変更する
    setTasks: (state, tasks) => {
      state.tasks = tasks
    },
    addTask: (state, task) => {
      state.tasks.push(task)
    }
  },

  actions: {   // actionsでapi通信でのデータ取得などの処理を書く
    fetchTasks({ commit }) {
      axios.get('tasks')   //  axiosを使って'/tasks'にgetリクエストを送る
        .then(res => {
          commit('setTasks', res.data)  
        //  mutationsの'setTasks'を呼び出して、第二引数に取得したタスク一覧データを渡す
        })
        .catch(err => console.log(err.response));
    },

    createTask({ commit }, task) {
      return axios.post('tasks', task)   //  axiosで'/tasks'にpostリクエストを送る
        .then(res => {
          commit('addTask', res.data)
         // mutationsの'addTask'を呼び出して、第二引数で取得したtaskのデータを渡す
        })
    }
  },
})

コンポーネントを修正

<template>
  <div>
    <div class="d-flex">
      <div class="col-4 bg-light rounded shadow m-3 p-3">
        <div class="h4">TODO</div>
        <div v-for="task in tasks" 
            :key="task.id" 
            :id="'task-' + task.id"
            class="bg-white border shadow-sm rounded my-2 p-4"
            @click="handleShowTaskDetailModal(task)">
          <span>{{ task.title }}</span>
        </div>
        <button @click="handleShowTaskCreateModal" type="button" class="btn btn-secondary">
          タスクを追加
        </button>
      </div>
    </div>
    <div class="text-center">
      <router-link :to="{ name: 'TopIndex' }" class="btn btn-dark mt-5">戻る</router-link>
    </div>
    <transition name="fade">
      <TaskDetailModal v-if="isVisibleTaskDetailModal" @close-modal="handleCloseTaskDetailModal" :task="taskDetail" />
    </transition>
    <transition name="fade">
      <TaskCreateModal v-if="isVisibleTaskCreateModal" 
          @close-modal="handleCloseTaskCreateModal"
          @create-task="handleCreateTask" />
    </transition>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'
//  vuexで定義したgetterとacitonsをインポート

import TaskDetailModal from './components/TaskDetailModal'
import TaskCreateModal from './components/TaskCreateModal'

export default {
  components: {
    TaskDetailModal,
    TaskCreateModal
  },
  name: "TaskIndex",
  data() {
    return {
      taskDetail: {},
      isVisibleTaskDetailModal: false,
      isVisibleTaskCreateModal: false
    }
  },
  computed: {
    ...mapGetters([ 'tasks' ])
   // mapGettersヘルパーを使ってcomputed(算出プロパティ)とマッピングさせる
  // stateのtasksの値が変わると更新される
  },

  created() {
    this.fetchTasks();   // actionsで定義した'fetchTasks'メソッドを呼び出す
  },

  methods: {
    ...mapActions([ 'fetchTasks', 'createTask' ]),
//  マッピングさせて直接storeのメソッドを呼べるようにする

    async handleCreateTask(task) {
      try {
        this.createTask(task);   //  actionsで定義した'createTask'メソッドを呼び出す
        this.handleCloseTaskCreateModal();
      } catch(error) {
        console.log(error)
      }
    },
    handleCloseTaskDetailModal() {
      this.isVisibleTaskDetailModal = false;
      this.taskDetail = {};
    },
    handleShowTaskDetailModal(task) {
      this.isVisibleTaskDetailModal = true;
      this.taskDetail = task;
    },
    handleCloseTaskCreateModal() {
      this.isVisibleTaskCreateModal = false;
    },
    handleShowTaskCreateModal() {
      this.isVisibleTaskCreateModal = true;
    },
  }
}
</script>

<style scoped>
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>