Vue 3でTODOを作る

webvuetypescript

Vue CLIをインストールしプロジェクトを作成する。

$ npm install -g @vue/cli
$ vue --version
@vue/cli 4.5.13

$ vue create todo
$ tree -I node_modules todo
.
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
└── src
    ├── App.vue
    ├── assets
    │   └── logo.png
    ├── components
    │   └── HelloWorld.vue
    └── main.js

エントリーポイント main.js を見るとVue 3からのGlobal API、createApp()が使われている。

$ cat src/main.js
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

まずは入力フォームを作っていく。 .vueSingle File Componentsのファイル。

v-onまたは@でイベントをハンドリングする。 v-modelmodelValue propをinput.valueにバインドし、 入力時にemitされる update:modelValue eventで更新することで双方向にバインディングする。 Vue 2ではvalue propを input eventによって更新していた。

$ cat src/components/Form.vue
<template>
  <div>
    <input v-model="title" placeholder="TODO">
    <button @click="add">add</button>
  </div>
</template>

<script>
export default {
  name: 'Form',
  data() {
    return {
      title: '',
    }
  },
  methods: {
    add() {
      this.$emit('add', this.title)
      this.title = ''
    }
  }
}
</script>

<style scoped>
div {
    background-color: #ddeedd;
}
</style>

次にTODOを表示するリストを作る。

propsとしてtodoを受け取り、 v-forでループさせ、v-ifでレンダリングする条件を渡す。 v-ifv-for より先に評価されるので、同じ要素に含められない。 v-bind または : を用いることで文字列以外の定数や変数を渡すことができる。

$ cat src/components/List.vue
<template>
  <div>
    <ol>
        <template v-for="(todo, index) in todos" :key="todo.id">
            <li v-if="!todo.done">
                {{ todo.title }}
                <button @click="done(index)">done</button>
            </li>
        </template>
    </ol>
  </div>
</template>

<script>
export default {
  name: 'List',
  props: {
      allTodos: Array
  },
  data() {
    return {
      todos: this.allTodos,
    }
  },
  methods: {
    done(index) {
      this.todos[index].done = true
    }
  }
}
</script>

<style scoped>
div {
    background-color: #eeddee;
}
</style>

最後にこれらのコンポーネントを配置する。Formで発火したイベントを受け取りデータを更新するとListも更新される。

$ cat src/App.vue
<template>
  <Form @add="addTodo" />
  <List :allTodos="allTodos" />
</template>

<script>
import Form from './components/Form.vue'
import List from './components/List.vue'

export default {
  name: 'App',
  components: {
    Form,
    List
  },
  data: () => { 
    return {
      allTodos: []
    }
  },
  methods: {
    addTodo(title) {
      this.allTodos.push({id: this.allTodos.length + 1, title: title, done: false})
    }
  }
}
</script>

TODO