【Vue.js】vuexを使ってタスクの更新と削除機能を実装

はじめに

vuexを使ってタスクの更新と削除機能を実装します。

コンポーネントから詳細ページモーダルを呼び出して、

その詳細ページに編集ボタンと削除ボタンがあり、

編集ボタンを押すと、編集ページのモーダルが表示される。

タスク編集ページを作成

<template>
  <div :id="'task-edit-modal-' + task.id">
    <div class="modal" @click.self="handleCloseModal">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-body">
            <div class="from-group mb-3">
              <label for="title">タイトル</label>
              <input type="text" class="form-control" 
                     id="title" v-model="task.title">
                               // v-modelを使って編集内容をtask.titleに代入
            </div>
            <div class="from-group mb-3">
              <label for="description">説明文</label>
              <textarea class="form-control"
                        id="description"
                        rows="5" 
                        v-model="task.description"></textarea>
                               // v-modelを使って編集内容をtask.titleに代入
            </div>
            <div class="d-flex justify-content-between">
              <button type="button"
                      class="btn btn-success"
                      @click="hadleUpdateTask">更新</button>
              <button type="button" 
                      class="btn btn-secondary" 
                 @click="handleCloseModal">閉じる</button>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="modal-backdrop show"></div>
  </div>
</template>

<script>
export default {
  name: 'TaskEditModal',
  props: {  // propsを使って親コンポーネントからデータを受け取る
    task: {
      id: {
        type: Number,
        required: true
      },
      title: {
        type: String,
        required: true
      },
      description: {
        type: String,
        required: true
      }
    }
  },
  methods: {
    handleCloseModal() {
      this.$emit('close-modal')  // $emitメソッドを使って親コンポーネントのイベントを発火
    },
    hadleUpdateTask() {
      this.$emit('update-task', this.task)
    }
  }
}
</script>

<style scoped>
.modal {
  display: block;
}
</style>

詳細ページを修正

<template>
  <div :id="'task-detail-modal-' + task.id">
    <div class="modal" @click.self="handleCloseModal">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title">{{ task.title }}</h5>
            <button type="button" class="close" @click="handleCloseModal">
              <span>&times;</span>
            </button>
          </div>
          <div class="modal-body" v-if="task.description">
            <p>{{ task.description }}</p>
          </div>
          <div class="modal-footer">
            <button type="button" 
                    class="btn btn-success" 
                    @click="handleShowTaskEditModal">編集</button>
                  //  編集ボタンと削除ボタンを追加
              <button type="button" 
                    class="btn btn-danger" 
                    @click="handleDeleteTask">削除</button>  
            <button type="button" 
                    class="btn btn-secondary" 
                    @click="handleCloseModal">閉じる</button>
          </div>
        </div>
      </div>
    </div>
    <div class="modal-backdrop show"></div>
  </div>
</template>

<script>
export default {
  name: "TaskDetailModal",
  props: {
    task: {
      title: {
        type: String,
        required: true
      },
      description: {
        type: String,
        required: true
      }
    }
  },
  methods: {
    handleCloseModal() {
      this.$emit('close-modal')
    },
    // 編集ボタンを押したときに親コンポーネントの'show-edit-modal'イベントを発火させる。
   // 第二引数にはtaskのデータを渡す。
    handleShowTaskEditModal() {
      this.$emit('show-edit-modal', this.task)
    },

   // 削除ボタンを押したときに親コンポーネントの'delete-task'イベントを発火させる。
  // 第二引数にはtaskのデータを渡す。
    handleDeleteTask() {
      this.$emit('delete-task', this.task)
    }
  }
}
</script>

<style scoped>
 .modal {
  display: block;
}
</style>

コンポーネントを修正

<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 d-flex align-items-center"
            @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"
          @show-edit-modal="handleShowTaskEditModal"
         // 詳細ページで編集ボタンを押した時のイベント
          @delete-task="handleDeleteTask" />
        // 詳細ページで削除ボタンを押した時のイベント
    </transition>

    <transition name="fade">
      <TaskCreateModal v-if="isVisibleTaskCreateModal" 
          @close-modal="handleCloseTaskCreateModal"
          @create-task="handleCreateTask" />
    </transition>

    // 編集ページモーダルのコンポーネントを呼び出す。
    // boolean形式で表示非表示を設定
    <transition name="fade">
      <TaskEditModal v-if="isVisibleTaskEditModal" 
          @close-modal="handleCloseTaskEditModal"
          :task="taskEdit"  //  taskのデータを渡す
          @update-task="handleUpdateTask"/>
         // 編集ページで更新ボタンを押した時のイベント
    </transition>
  </div>
</template>

<script>
import { mapGetters, mapActions } from 'vuex'
import TaskDetailModal from './components/TaskDetailModal'
import TaskCreateModal from './components/TaskCreateModal'
import TaskEditModal from './components/TaskEditModal'
// それぞれのコンポーネントをインポート

export default {
  components: {
    TaskDetailModal,
    TaskCreateModal,
    TaskEditModal
  },
  name: "TaskIndex",
  data() {
    return {
      taskDetail: {},
      isVisibleTaskDetailModal: false,
      isVisibleTaskCreateModal: false,
      isVisibleTaskEditModal: false,
      taskEdit: {} // 編集ページにデータを渡すためのプロパティを定義
    }
  },
  computed: {
    ...mapGetters([ 'tasks' ])
  },

  created() {
    this.fetchTasks();
  },

  methods: {
    ...mapActions([ 'fetchTasks', 'createTask', 'updateTask', 'deleteTask' ]),
   // vuexのactionsのメソッドを直接呼び出せるようにするためにマッピングする

    async handleCreateTask(task) {
      try {
        await this.createTask(task);
        this.handleCloseTaskCreateModal();
      } catch(error) {
        console.log(error)
      }
    },

    // 編集ページで更新ボタンが押された時に発火するメソッド
    async handleUpdateTask(task) {
      try {
        await this.updateTask(task);  // vuexのactionsで定義しているメソッド
        this.handleCloseTaskEditModal();
      } catch(error) {
        console.log(error)
      }
    },

    async handleDeleteTask(task) {
      try {
        await this.deleteTask(task);  // vuexのactionsで定義しているメソッド
        this.handleCloseTaskDetailModal();
      } 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;
    },
    handleCloseTaskEditModal() {
      this.isVisibleTaskEditModal = false;
    },
    handleShowTaskEditModal(task) {
      this.taskEdit = Object.assign({}, task);
  // taskのデータを空のオブジェクトにマージしてから値渡しする。
  // 参照渡しをすると更新ボタンを押さずに編集モーダルを閉じても値が更新されてしまう。v-modelを使っているため
      this.isVisibleTaskEditModal = true;
      this.isVisibleTaskDetailModal = false;
    },
  }
}
</script>

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

vuexの設定

import Vue from 'vue'
import Vuex from 'vuex'
import axios from '../plugins/axios'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    tasks: []
  },

  getters: {
    tasks: state => state.tasks
  },

  mutations: {
    setTasks: (state, tasks) => {
      state.tasks = tasks
    },
    addTask: (state, task) => {
      state.tasks.push(task)
    },

   //  更新の処理
    updateTask: (state, editTask) => {
      const index = state.tasks.findIndex(task => {
        return task.id == editTask.id
   // findIndexを使って編集するタスクのインデックス番号を取得
      })
      state.tasks.splice(index, 1, editTask)
   // spliceメソッドをを使ってタスクを更新する。(正確には交換)
    },

   // 削除の処理
    deleteTask: (state, deleteTask) => {
      state.tasks = state.tasks.filter(task => {
        return task.id != deleteTask.id
    //  filterメソッドを使って削除するタスク以外の要素の配列を代入する
      })
    }
  },

  actions: {
    fetchTasks({ commit }) {
      axios.get('tasks')
        .then(res => {
          commit('setTasks', res.data)
        })
        .catch(err => console.log(err.response));
    },
    createTask({ commit }, task) {
      return axios.post('tasks', task)
        .then(res => {
          commit('addTask', res.data)
        })
    },

   // axiosを使って該当のURLにpatchリクエストを送る。
    updateTask({ commit }, task) {
      return axios.patch(`tasks/${task.id}`, task)
        .then(res => {
          commit('updateTask', res.data)
      // 取得したtaskのデータを引数にしてmutationsの'updateTask'を実行する
        })
    },

   // axiosを使って該当のURLにdeleteリクエストを送る。
    deleteTask({ commit }, task) {
      return axios.delete(`tasks/${task.id}`, task)
        .then(res => {
          commit('deleteTask', res.data)
      // 取得したtaskのデータを引数にしてmutationsの'deleteTask'を実行する
        })
    }
  },
})

完成画面

f:id:study-output:20210623181211p:plain

編集します。

f:id:study-output:20210623181225p:plain

更新します。

f:id:study-output:20210623181238p:plain

更新できました!

続いて削除します。

f:id:study-output:20210623181255p:plain

削除できました!

f:id:study-output:20210623181311p:plain