LoginSignup
0
3

More than 3 years have passed since last update.

Vueを使ってToDoリストを作ってみた

Posted at

Vue.jsの公式チュートリアル、ずいぶん充実しているなぁ。
だけど、一通り終わっちゃったし、実務で使うまでにもういくつか練習してみたいな。

ということで、こちらのページで紹介されているチュートリアル「ToDoリストを作りながら学習しよう!」をベースに、ToDoリスト作ってみました。
https://cr-vue.mio3io.com/tutorials/todo.html

変更した点

routerを使った

元サイトではindex.html、main.css、main.jsだけで実装していますが、せっかくの練習なので、vue-routerをつかいコンポーネントを外部から読み込んでくる形にしました(1ページだから意味ないけど)。以下構成です。
- App.vue
- main.js
- router
 └ index.js
- views
 └ Top.vue

ローカルストレージをそのまま使いました

ローカルストレージAPIから抜き出してくるのではなく、こちらに書いてある方法で直接ローカルストレージに読み書きしました。
https://jp.vuejs.org/v2/cookbook/client-side-storage.html

完成図

cssは上記サイトからコピペさせていただきましたm(__)m
image.png

コード

App.vue
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
</style>

main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router/'

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const router = new VueRouter({
  mode : 'history',
  base : process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'top',
      component: () => import('../views/Top.vue')
    }
  ]
})

export default router
Top.vue
<template>
  <div class="top">
    <h2>TODOリスト</h2>

    <label v-for="(option, key, index) in options" :key="index">
      <input type="radio"
        v-model="current"
        v-bind:value="option.value" />{{ option.label }}
    </label>

    <span>{{ computedTodos.length }} 件を表示中</span>
    <table>
      <thead>
        <tr>
          <th class="id">ID</th>
          <th class="comment">コメント</th>
          <th class="state">状態</th>
          <th class="button">-</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="item in computedTodos" v-bind:key="item.id">
          <th>{{ item.id }}</th>
          <td>{{ item.comment }}</td>
          <td class="state">
            <button @click="doChangeState(item)">{{ labels[item.state] }} </button>
          </td>
          <td class="button">
            <button @click.ctrl="doRemove(item)">削除</button>
          </td>
        </tr>
      </tbody>
    </table>
    <p>※削除ボタンはコントロールキーを押しながらクリックしてください</p>

    <h2>新しい作業の追加</h2>
    <form @submit.prevent="doAdd" class="add-form">
      <p>
        <span>コメント</span>
        <input type="text" ref="comment">
        <button type="submit">追加</button>
      </p>
    </form>
  </div>
</template>

<script>
export default{
  name: 'top',
  data(){
    return {
      todos: [],
      options: [
        { value: -1, label: 'すべて' },
        { value:  0, label: '作業中' },
        { value:  1, label: '完了' }
      ],
      current: -1,
      uid: 0
    }
  },
  computed: {
    labels(){
      return this.options.reduce(function(a,b){
        return Object.assign(a, { [b.value]: b.label })
      }, {})
    },
    computedTodos: function(){
      return this.todos.filter(function(el) {
        return this.current < 0 ? true: this.current === el.state
      }, this)
    }
  },
  created(){
    if(localStorage.getItem('todos')){
      try{
        this.todos = JSON.parse(localStorage.getItem('todos'))
      } catch(e){
        localStorage.removeItem('todos')
      }
    }

    if(localStorage.getItem('uid')){
      this.uid = localStorage.getItem('uid')
    }
  },
  methods:{
    doAdd : function(){
      var comment = this.$refs.comment
      if(!comment.value.length){
        return
      }
      this.uid += 1
      this.todos.push({
        id: this.uid,
        comment: comment.value,
        state: 0
      })
      localStorage.setItem('uid',this.uid)
      comment.value = ''
    },
    doChangeState: function(item){
      item.state = item.state ? 0 : 1
    },
    doRemove: function(item){
      var index = this.todos.indexOf(item)
      this.todos.splice(index,1)
    }
  },
  watch: {
    todos: {
      handler: function(todos) {
        localStorage.setItem('todos',JSON.stringify(todos))
      },
      deep: true
    }
  }
}
</script>

<style>
省略 
</style>

v-forでキー指定

Top.vue
    <label v-for="(option, key, index) in options" :key="index">
      <input type="radio"
        v-model="current"
        v-bind:value="option.value" />{{ option.label }}
    </label>

上記みたくキーを指定してあげないと下記みたく怒られたので、キーを指定しました。

5:5  error  Elements in iteration expect to have 'v-bind:key' directives  vue/require-v-for-key

✖ 1 problem (1 error, 0 warnings)

clickを省略記法に変えました

Top.vue
          <td class="state">
            <button @click="doChangeState(item)">{{ labels[item.state] }} </button>
          </td>
          <td class="button">
            <button @click.ctrl="doRemove(item)">削除</button>
          </td>

Vue.jsでは"v-on:click"を"@click"のように省略して書けるので省略しました。

ローカルストレージへの読み書き

Top.vue
  created(){
    if(localStorage.getItem('todos')){
      try{
        this.todos = JSON.parse(localStorage.getItem('todos'))
      } catch(e){
        localStorage.removeItem('todos')
      }
    }
  }

こちらを参考にしてローカルストレージへの読み書きを直接する方法に変えました。
https://jp.vuejs.org/v2/cookbook/client-side-storage.html

Top.vue
    doAdd : function(){
      var comment = this.$refs.comment
      if(!comment.value.length){
        return
      }
      this.uid += 1
      this.todos.push({
        id: this.uid,
        comment: comment.value,
        state: 0
      })
      localStorage.setItem('uid',this.uid)
      comment.value = ''
    }

IDの更新は、uidという変数をローカルストレージとVueとで共有して更新していくやり方にしました。

Top.vue
  watch: {
    todos: {
      handler: function(todos) {
        localStorage.setItem('todos',JSON.stringify(todos))
      },
      deep: true
    }
  }

一瞬詰まったのが、ローカルストレージへのオブジェクトの保存。
ローカルストレージには配列やオブジェクトなど複雑なものは入れられないので、Stringに変換する必要があります。

シンプルで取り組みやすいチュートリアルをありがとうございました!
コンポーネント化とかvue-routerとかもっと触ってみたいので、次は複数ページにわたるアプリを作ってみたいと思います!

0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3