【Vue.js】eslintを使って構文チェックを自動化する

はじめに

eslintを使ってvue.jsの構文チェックを自動化する方法について

eslintの設定

まずは下記のコマンドでeslintをインストールします。

$ yarn add --dev eslint eslint-plugin-vue

続いてアプリケーションのルートディレクトリに.eslintrc.jsファイルを作成します。

$ touch .eslintrc.js 

作成したファイルに下記のコードを書きます。

module.exports = {
  "extends": [
    "plugin:vue/recommended",
  ]
};

plugin:vue/recommendedの記述をすることで、vue.jsスタイルガイドの優先度A〜Cのルールに対してチェックを行ってくれます。

続いて、package.jsoneslintのコマンドを登録します。

  "scripts": {
    "lint": "eslint --ext .js,.vue app/javascript",
    "lint-fix": "eslint --fix --ext .js,.vue app/javascript"
  },

構文チェック

eslintの設定が終わったら、下記コマンドで構文チェックをします。

$ yarn run lint

上記のコマンドを実行すると、たくさんのwarningが表示されるかと思います。

それを全て手動で修正するのは大変なので、下記コマンドを打って自動で修正します。

$ yarn run lint-fix

自動修正できない箇所に関しては、手動で修正します。

【Vue.js】slotを使って親コンポーネントからテンプレートを差し込む

はじめに

v-slotを使って親コンポーネントから子コンポーネントにテンプレートを差し込む方法について

コンポーネント

<template>
    <div class="row">
      <childComponent :tasks="todoTasks"
                taskListId="todo-list"
                @handleShowTaskDetailModal="handleShowTaskDetailModal">
        <template v-slot:header>       // v-slotで任意の名前を指定
          <div class="h4">TODO</div>  // この記述が差し込まれる
        </template>
      </childComponent>

      <childComponent :tasks="doingTasks"
                taskListId="doing-list"
                @handleShowTaskDetailModal="handleShowTaskDetailModal">
        <template v-slot:header>       // v-slotで任意の名前を指定
          <div class="h4">DOING</div>  // この記述が差し込まれる
        </template>
      </childComponent>

      <childComponent:tasks="doneTasks"
                taskListId="done-list"
                @handleShowTaskDetailModal="handleShowTaskDetailModal">
        <template v-slot:header>       // v-slotで任意の名前を指定
          <div class="h4">DONE</div>  // この記述が差し込まれる
        </template>
      </childComponent>
    </div>
</template>

コンポーネント

<template>
  <div class="col-12 col-lg-4">
    <div :id="taskListId" class="bg-light rounded shadow m-3 p-3">
      <slot name="header"></slot> // ここに差し込まれる
       // 親コンポーネントで指定した任意の名前を指定する
      <template v-for="task in tasks">
        <TaskItem :key="task.id" :task="task" @handleShowTaskDetailModal="$listeners['handleShowTaskDetailModal']" />
      </template>
    </div>
  </div>
</template>

上記のようにv-slotを使うことで、同じコンポーネントを呼びだしながら、部分的にテンプレートを変えることができる。

【Vue.js】孫コンポーネントから親コンポーネントにデータを渡す

はじめに

コンポーネントから親コンポーネントにデータを渡す方法について

データを渡す方法

コンポーネントから親コンポーネントにデータを渡すには、孫コンポーネントと子コンポーネント$emitメソッドを使う方法でもできますが、

コンポーネント$listenersメソッドを使うことでもっとわかりやすくなります。

コンポーネント

<template>
  <div :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>
</template>

<script>
export default {
  name: 'TaskItem',
  props: {
    task: {
      type: Object,
      required: true
    }
  },

// $emit を使って子コンポーネントのメソッドを呼びデータを渡す
  methods: {
    handleShowTaskDetailModal(task) {
      this.$emit('handleShowTaskDetailModal', task)
    }
  }
}
</script>

コンポーネント

<template>
  <div class="col-12 col-lg-4">
    <div :id="taskListId" class="bg-light rounded shadow m-3 p-3">
      <slot name="header">タスク区分</slot>
      <template v-for="task in tasks">
        <TaskItem :key="task.id" :task="task" 
                  @handleShowTaskDetailModal="$listeners['handleShowTaskDetailModal']" />
                  // $listenersを使って孫コンポーネントのデータを親コンポーネントに渡す
      </template>
    </div>
  </div>
</template>

<script>
import TaskItem from './TaskItem'

export default {
  components: {
    TaskItem
  },
  name: "TaskList"

コンポーネント

      <TaskList :tasks="todoTasks"
                taskListId="todo-list"
                @handleShowTaskDetailModal="handleShowTaskDetailModal">
               // このイベントが発火  
<template>
......
......
        <template v-slot:header>
          <div class="h4">TODO</div>
        </template>
      </TaskList>
.......
.......
</template>

<script>
.......
import TaskList from './components/TaskList'

export default {
  components: {
    TaskList
  },

//  上記のイベントが発火して下記メソッドが実行される
methods: {
    handleShowTaskDetailModal(task) {
      this.isVisibleTaskDetailModal = true;
      this.taskDetail = task;
    },
.........
.........
</script>

【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

メモ【javascript】

Object.assign

Object.assignはオブジェクトをマージすることができます。

const object_1 = { a: 1, b: 2 }

const object_2 = { c: 3, d: 4 }

const united_object = Object.assign(object_1, object_2)


console.log(object_1)
=> { a: 1, b: 2, c: 3, d: 4 }

console.log(object_2)
=> { c: 3, d: 4 }

console.log(united_object)
=> { a: 1, b: 2, c: 3, d: 4 }

上記のように第一引数に第二引数をマージすることができます。

同じプロパティを持っている場合は上書きされます。

const object_1 = { a: 1, b: 2 }

const object_2 = { b: 3, c: 4 }

const united_object = Object.assign(object_1, object_2)


console.log(object_1)
=> { a: 1, b: 3, c:4 }

console.log(object_2)
=> { b: 3, c: 4 }

console.log(united_object)
=> { a: 1, b: 3, c:4 }

spliceメソッド

spliceメソッドは配列の要素を削除したり追加したりできます。

また削除と追加を同時にして要素を交換できます。

第一引数だけ指定した場合は、指定したインデックス番号以降の要素を削除します。

var array = ['A', 'B', 'C', 'D', 'E']

array.splice(2)
=> ['A', 'B']

インデックス番号が2の要素であるC以降の要素が削除される。

第二引数を指定すると、第一引数で指定したインデックス番号から第二引数で指定した数の分だけ要素を削除します。

var array = ['A', 'B', 'C', 'D', 'E']

array.splice(2, 2)
=> ['A', 'B', 'E']

インデックス番号が2の要素Cと要素Dの二つ分が削除されます。

第三引数を指定すると、第一引数で指定したインデックス番号から第二引数で指定した数の分だけ要素を削除し、

第三引数で指定した要素を追加します。

第二引数を0にした場合は、第三引数の要素を追加するだけになります。

var array = ['A', 'B', 'C', 'D', 'E']

array.splice(1, 0, '追加します' )
=> ['A', '追加します', 'B', 'C', 'D', 'E']

第二引数を1にすると、要素の交換になります。

var array = ['A', 'B', 'C', 'D', 'E']

array.splice(1, 1, '追加します' )
=> ['A', '追加します', 'C', 'D', 'E']

findIndex

findIndexは配列の要素から特定のインデックス番号を返します。

const b = 'B'

const array = ['A', 'B', 'C', 'D', 'E']

const index = array.findIndex(item => item === b)

console.log(index)
=> 1

【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>

【Vue.js】タスク追加ページのモーダルをつくる

はじめに

タスクを追加するページのモーダルをコンポーネントとして作成します。

タスク追加ページを作成

$ touch app/javascript/pages/task/components/TaskCreateModal.vue
<template>
  <transition name="fade">
    <div id="task-create-modal">
      <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を使って入力された内容をそのまま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を使って入力された内容をそのままdescriptionに代入
              </div>
              <div class="d-flex justify-content-between">
                <button type="button" 
                        class="btn btn-success"
                        @click="handleCreateTask">追加</button> 
                <button type="button"
                        class="btn btn-secondary" 
                        @click="handleCloseModal">閉じる</button>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="modal-backdrop show"></div>
    </div>
  </transition>
</template>

<script>
export default {
  name: 'TaskCreateModal',
  data() {  
    return {
      task: {
        title: '',
        description: ''
      }
    }
  },
  methods :{
    handleCloseModal() {   // 閉じるボタンを押した時に親コンポーネントの'close-modal'イベントを発火
      this.$emit('close-modal')
    },

                           // 第二引数で入力されたデータを渡す。
    handleCreateTask() {   // 追加ボタンを押したときに親コンポーネントの'create-task'イベントを発火
      this.$emit('create-task', this.task)
    }
  }
}
</script>

<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"
             @click="handleShowTaskDetailModal(task)"
             class="bg-white border shadow-sm rounded my-2 p-4">
          <span>{{ task.title }}</span>
        </div>
        <button @click="handleShowTaskCreateModal"
                type="button"
                class="btn btn-secondary">
          タスクを追加
        </button>
      </div>
    </div>
    <div class="text-center">
      <router-link to="/" class="btn btn-dark mt-5">戻る</router-link>
    </div>
    <TaskDetailModal v-if="isVisibleTaskDetailModal"
                     :task="taskDetail"
                     @close-modal="handleCloseTaskDetailModal" />
    <TaskCreateModal v-if="isVisibleTaskCreateModal"
                     @close-modal="handleCloseTaskCreateModal"
                     // 子コンポーネントで閉じるボタンを押した時のイベント
                     @create-task="handleCreateTask"/>
                    // 子コンポーネントで追加ボタンを押した時のイベント
  </div>
</template>

<script> import TaskDetailModal from './components/TaskDetailModal' import TaskCreateModal from './components/TaskCreateModal' // 追加ページのコンポーネントをインポート

export default { components: { TaskDetailModal TaskCreateModal }, name: 'TaskIndex', data() { return { tasks: [], taskDetail: {}, isVisibleTaskDetailModal: false, isVisibleTaskCreateModal: false, // デフォルトでは非表示 } }, created() { this.fetchTasks(); }, methods: { fetchTasks() { this.$axios.get('tasks') .then(res => this.tasks = res.data) .catch(err => console.log(err.status)); }, handleCreateTask(task) {
try { this.$axios.post('tasks', task) // axiosを使って/tasksにpostリクエストを送る .then(res => { // ↓ postリクエストが成功した場合に擬似リロード処理を実行 this.$router.go({path: this.$router.currentRoute.path, force: true}) }); this.handleCloseTaskCreateModal(); } catch(error) { // postリクエストが失敗した場合のエラーをコンソールに表示 console.log(error) } }, handleShowTaskDetailModal { this.isVisibleTaskDetailModal = true; this.taskDetail = task; }, handleCloseTaskDetailModal() { this.isVisibleTaskDetailModal = false; this.taskDetail = {}; }, // ↓ 追加ページのモーダルの表示非表示の処理 handleShowTaskCreateModal() { this.isVisibleTaskCreateModal = true; }, handleCloseTaskCreateModal() { this.isVisibleTaskCreateModal = false; } } } </script>

<style scoped> </style>

完成画面

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

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