【Vue.js】簡単なToDoアプリの制作と学習メモ

#きっかけ
これまでVueを用いてアプリを作ったりしていたのですが，Vueについての基本的なことが全然理解できていなかったことやFirebaseでサイトを公開してみたかったため学習を兼ねて作ってみました．

https://twitter.com/temple_c_tech/status/1380699559311994882

#完成物
ToDoアプリ

https://vue-todo-146d8.web.app/#/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1009493/7215b4ae-bc3c-889d-50a0-428018d8627d.png)



#構成
"Index.vue(src/pages/Index.vue)"という大枠のページの中に,

* Todoを書きこむ"TodoForm.vue(src/components/TodoForm.vue)"
* 書き込んだTodoを表示させる"TodoList.vue(src/components/TodoList.vue)"

の２つのコンポーネントを組み込んだ形になります．

スタイルはBootstrap-Vueを使いました．

https://bootstrap-vue.org/docs

#インポート
Bootstrapの公式サイトに従ってコマンドを打ち込みます．

```
npm install vue bootstrap bootstrap-vue
```

次にindex.js内でimport

```index.js
import Vue from 'vue'
import Router from 'vue-router'
import Index from '@/pages/Index'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// Import Bootstrap an BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

// Make BootstrapVue available throughout your project
Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Index',
      component: Index
    }
  ]
})
```

#Index.vue
大枠となるページのファイル

```Index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <todo-form @handleParentAddTodo="handleParentAddTodo" />
    <todo-list
      :todos="todos"
      @handleParentDeleteTodo="handleParentDeleteTodo"
      @handleParentCompleteTodo="handleParentCompleteTodo" />
  </div>
</template>

<script>
// TodoForm,TodoList読み込み
import TodoForm from '@/components/TodoForm'
import TodoList from '@/components/TodoList'

export default {
  name: 'Todo',
  components: {
    TodoForm,
    TodoList
  },
  data () {
    return {
      // データバインディング
      todos: []
    }
  },
  methods: {
    // v-onでメソッド呼び出し
    // Todoリスト追加処理
    handleParentAddTodo (value) {
      if (value) {
        this.todos.unshift({ text: value, complete: false })
      }
    },
    // Todoリスト完了処理
    // この処理でcompleteの真偽を逆転させる
    handleParentCompleteTodo (index) {
      this.todos[index].complete = !this.todos[index].complete
    },
    // Todoリスト削除処理
    // spliceで引数の一行を消去
    handleParentDeleteTodo (index) {
      this.todos.splice(index, 1)
    }
  }
}
</script>

<style>

</style>

```

#TodoForm.vue
Todoリストを書き込み送信するファイル

handleAddTodo関数の$emitで子コンポーネント(TodoForm.vue)から親コンポーネント(Index.vue)へイベントを放っている

```TodoForm.vue
<template>
  <b-container>
    <b-input-group>
      <!-- v-model属性で双方向ディバイディング -->
      <b-form-input type="text" v-model="value"/>
      <b-input-group-append>
        <b-button variant="primary" @click="handleAddTodo()">送信</b-button>
      </b-input-group-append>
    </b-input-group>
  </b-container>
</template>

<script>
export default {
  name: 'TodoForm',
  data () {
    return {
      value: ''
    }
  },
  methods: {
    // 送信ボタンクリック時に処理される関数
    handleAddTodo () {
      // Index.vueのhandleParentAddTodoにフォーム内の値を引数に発火
      this.$emit('handleParentAddTodo', this.value)
      this.value = ''
    }
  }

}
</script>

<style>

</style>

```

#TodoList.vue
フォームで送信されたTodoリストを表示したり，進捗を操作するファイル

TodoFormと同様にhandleCompleteTodo関数,handleDeleteTodo関数の$emitで子コンポーネント(TodoList.vue)から親コンポーネント(Index.vue)へイベントを放っている

```TodoList.vue
<template>
  <b-container>
    <!-- for文でTodoリストをひとつづつ処理 -->
    <b-row :key="index" v-for="(todo, index) in todos" class="mt-2">
      <!-- v-bindのclass属性によりcomplete時にstyleが適用 -->
      <b-col cols="8" :class="[{line: todo.complete}, 'text-left']">
        <h5>{{todo.text}}</h5>
      </b-col>
      <b-col cols="4" class="text-right">
        <b-button @click="handleCompleteTodo(index)"
        :variant="todo.complete ? '' : 'success'">
          {{ todo.complete ? '完了' : '未完了'}}
          </b-button>
        <b-button @click="handleDeleteTodo(index)" variant="danger">削除</b-button>
      </b-col>
    </b-row>
  </b-container>
</template>

<script>
export default {
  name: 'TodoList',
  // 親コンポーネントからv-bindで渡された値（todos）を受け取る
  props: ['todos'],
  methods: {
    // 完了ボタンクリック時に処理される関数
    handleCompleteTodo (index) {
      // Index.vueのhandleParentCompleteTodoにフォーム内の値を引数に発火
      this.$emit('handleParentCompleteTodo', index)
    },
    // 削除ボタンクリック時に処理される関数
    handleDeleteTodo (index) {
      // Index.vueのhandleParentDeleteTodoにフォーム内の値を引数に発火
      this.$emit('handleParentDeleteTodo', index)
    }
  }
}
</script>

<style scoped>
  .line {
    text-decoration: line-through;
  }
</style>

```
#FirebaseでHosting
以下の記事を参考にさせていただきました

https://qiita.com/gupuru/items/25a6722f6f802d3a5250

#まとめ
Todoアプリの制作を通して，v-model,v-bind,v-onについて一通りさわることができた．

propsと$emitという親コンポーネントと子コンポーネントをつなぐ概念について理解できた．

Firebaseで簡単にサイト公開をすることができた．