【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>×</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'を実行する }) } }, })
完成画面
編集します。
更新します。
更新できました!
続いて削除します。
削除できました!