3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Vue.js 編 - 初心者こそ、Docker を使おう! 

Last updated at Posted at 2019-10-23

簡単なTodo アプリを作りましょう

  • オリジナルはdotinstall さんの vue.js のtodoアプリをアレンジしたものですので詳しくはそちらをみてください。

今回は、Vue.js を使って、Todoに必要な処理の練習をします。

Todo タスクを追加してみましょう。

lang指定でts,stylus を指定していますが、typescript, stylus css 用ですが、実際つかっているのは、javascript, css です。 stylusにすると、混在できますのでstylus を使っています。 typescript の混在できますので、型つけたければつければいいかと思います。

Vue.js は、input周りは、簡単に処理できるのがいいですね。

src/MyApp.vue

<template lang="pug">
  div.container
    Todo
</template>
<script lang="ts">
import Vue from 'vue';
import  Hello  from './Hello.vue';
import Todo from './Todo.vue';
export default Vue.extend({
  components: {
    Todo,
  }
})
</script>
<style lang="stylus">
body {
  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.container {
  width: 300px;
  margin: auto;
}
h1 {
  font-size: 16px;
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}
li {
  line-height: 1.5;
}
input[type="text"] {
  padding: 2px;
}

.done
  color gray
  text-decoration line-through


</style>

src/Todo.vue

<template lang="pug">
  div
    ul
      li(v-for="todo in todos")
        | {{ todo }}
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        "task 0",
        "task 1",
        "task 2",
      ]
    })
  },
  methods: {
    addItem() {
      this.todos.push(this.newItem);
      this.newItem = '';
    }
  }
})
</script>
  • コンテナ(uname linux でしたね)
uname
cd src
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug

delete / checkbox

削除もチェックボックスも実装が楽ですね。
id 追加しましたが、使っていません(笑) index指定で削除していますが、まずければ id に変えようと思いましたが、問題なさそうなので、使っていません。

src/Todo.vue

<template lang="pug">
  div
    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        input(type="checkbox" v-model="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        { id: 0, title: 'Task 0', isDone: false },
        { id: 1, title: 'Task 1', isDone: false },
        { id: 2, title: 'Task 2', isDone: true },
      ]
    })
  },
  methods: {
    addItem() {
      const newItem = {
        id: new Date().getTime().toString(36),
        title: this.newItem,
        isDone: false,
      }
      this.todos.push(newItem);
      this.newItem = '';
      console.log(newItem);

    },
    deleteItem(index) {
      this.todos.splice(index, 1)
    }
  }
})
</script>

複数削除

完了していないタスクは、computed で常に集計できるので、それを新しい配列にすれば、結果的に完了タスクを削除したことになりますね。

Todo.vue

<template lang="pug">
  div
    h1
      | todo
      span ( {{ remaining.length }} : {{ todos.length }} )
      button( @click="doneItemsPurge" ) Purge
    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        input(type="checkbox" v-model="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        { id: 0, title: 'Task 0', isDone: false },
        { id: 1, title: 'Task 1', isDone: false },
        { id: 2, title: 'Task 2', isDone: true },
      ]
    })
  },
  methods: {
    doneItemsPurge() {
      this.todos = this.remaining
    },
    addItem() {
      const newItem = {
        id: new Date().getTime().toString(36),
        title: this.newItem,
        isDone: false,
      }
      this.todos.push(newItem);
      this.newItem = '';
      console.log(newItem);
    },
    deleteItem(index) {
      this.todos.splice(index, 1)
    }
  },
  computed: {
    remaining() {
      return this.todos.filter( todo => {
        return !todo.isDone
      })
    }
  }

})
</script>

データの永続化

初心者むけでは大体 localStorage を使う例が多いので、 ここでは、Firebase の DB FireStore を使ってみようかと思います。

FireStore db は、googole のアカウントがあれば、利用できますので、firebase console で検索してみてください。 ここでは、プロジェクトの作成やfireStoreの有効にする方法は説明しませんので、利用出来る前提で始めていきます。
fireStore の 読み書きルール はすべて true の テストモードで行っています。

  • ファイル構成です。

├── Dockerfile
├── docker-compose.yml
├── firebase.js
└── src
    ├── Hello.vue
    ├── MyApp.vue
    ├── Todo.vue
    ├── dist
    ├── index.js
    ├── index.pug
    ├── package.json
    └── yarn.lock

ルードディレクトリに firebase.js を作っていますので、そこにfirebase の configをコピペしてください。

firebase.js

export const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};

MyApp.vue はほとんど変わっていませんが一応記載します。

src/MyApp.vue

<template lang="pug">
  div.container
    //- Hello
    Todo
</template>
<script lang="ts">

import Vue from 'vue';
import  Hello  from './Hello.vue';
import Todo from './Todo.vue';
export default Vue.extend({
  components: {
    Todo,
    Hello,
  }
})
</script>
<style lang="stylus">
body {
  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.container {
  width: 700px;
  margin: auto;
}
h1 {
  font-size: 16px;
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}
li {
  line-height: 1.5;
}
input[type="text"] {
  padding: 2px;
}

.done
  color gray
  text-decoration line-through


</style>

Todo.vue は、fireStore対応で大幅に変わっています。
リファクタリングもしていないので、見苦しい感じが残っていますが、実装の苦労の跡が見えていいのではないでしょうか(笑)
なるべく、fireStoreの機能を使って実装しようとしているので、データのやり取りは、fireStoreを毎回介して行っています。
マウントとアンマウントの時にfireStore にデータを渡したほうが動作が軽く楽かもしれませんが
fireStoreの練習には、こちらのほうがいいかと思います.

ラグが気になりますが、追加、削除、完了、未完了、一括削除ができているのOK でしょう。(笑)

async / await に慣れていない人へ

DB を使うと処理が非同期になるため、async / await を使っていますが、asyce/await に慣れていない人に一言いうと、処理に時間がかかりそうだと思うものの前に,すべてawait を 付けていけばいいです。
今回で言えば、データの読み書きで firestore に対してのと命令を書いた場合にあたります。
付けていないと、ほしいデータが入る前に次の処理が走ってしまって、空処理になるのを防いでいます。
ですから、時間がかかると思ったら、await を処理の前につけてください。
await を使うとき、親要素に async という呪文が必要なので、これを関数式に付けます。

awaitをつけたけれど、親要素がない場合は、そのあたりの処理一体を、関数にまとめてしまえばいいです。
そうすれば、非同期処理はできているはずです。

src/Todo.vue

<template lang="pug">
  div
    h1
      | todo
      span ( {{ remaining.length }} : {{ todos.length }} )
      button( @click="doneItemsPurge" ) Purge

    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        //- input(type="checkbox" v-model="todo.isDone")
        input(type="checkbox" @click="checkboxChange(index)" :checked="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }} : {{ todo.date }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo

</template>
<script lang="ts">

import firebase from 'firebase/app';
import 'firebase/firestore';
import { config } from '../firebase';

firebase.initializeApp(config);
const db = firebase.firestore()
const collection = db.collection('todos')

import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [],
      itemsId: [],
      storeItem: {},
      state: [],
    })
  },
  watch: {
   state: { // 更新チェック されたら、実行
     handler: async function(){
        const queryItems = await collection.orderBy("created_at", "desc").get()
        const qureyItemsData = queryItems.docs.map( item => item.data())
        const qureyItemsId = queryItems.docs.map( item => item.id )

        this.todos = qureyItemsData;
        this.itemsId = qureyItemsId;
    }, deep: true
   },
   todos: {
     handler: async function(){
    }, deep: true
   }
  },
  methods: {
    async checkboxChange(index) {
      let isDoneState = Boolean;
      const item = await collection.doc(this.itemsId[index])
      const isDone = await item.get()
        .then(function(doc) {
          isDoneState = doc.data().isDone
        })
        .catch( err => {
          console.log(err);
        })
      const changeItem = await item.update({
        isDone: !isDoneState,
      })
      this.state = ["checkboxUpdate"]

    },
    async deleteItem(index) {
      const item = await collection.doc(this.itemsId[index]).delete()
      this.state = ["delete"]
    },
    addItem() {
      this.storeItem = {
        id: new Date().getTime(),
        date: new Date().toLocaleString("ja"),
        title: this.newItem,
        isDone: false,
        created_at: firebase.firestore.FieldValue.serverTimestamp(),
      }
      const item = collection.add(this.storeItem);
      this.newItem = '';
      this.state = ["addItem"] // 状態更新通知用
    },
    async doneItemsPurge() {
      // this.todos = this.remaining

      const itemsQuery = await collection.where("isDone", "==" , true)
        .get()
        .then(function(querySnapshot) {
          querySnapshot.forEach(function(doc) {
            const item = collection.doc(doc.id).delete();
            console.log(item);
          });
        })
        .catch(function(error) {
          console.log("Error getting documents: ", error);
        })

      this.state = ["purge"] // 状態更新通知用
    },
  },
  computed: {
    remaining() {
      return this.todos.filter( todo => {
        return !todo.isDone
      })
    }
  },
  async mounted() {
    const query =  await collection.get()
    const items = query.docs.map( item => item.data())
    const itemsId = query.docs.map( item => item.id)
    this.todos = items;
    this.itemsId = itemsId;
    this.state =  items;
  }

})
</script>

これで、Vue.js での練習は、終了です。
お疲れ様でした。

次回は, この環境で、React を使ってみたいと思います

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?