Help us understand the problem. What is going on with this article?

Vue.js + FirebaseでTodoアプリを作る

More than 1 year has passed since last update.

普段はRailsを使っているのだけど、webサービス作るときの選択肢を増やすためにFirebaseを使えるようにしておこうと思った。

僕はjsのフレームワークではVue.jsが一番好きなので、今回はVue+Firebaseで簡単なTodoアプリを作ってみた。

todo_🔊.png

今回のTodoアプリでできることは以下の通り。ものすごくシンプルだし、見た目は気にしないでほしい。

  • タスクの作成
  • タスクの未完了/完了での表示切り替え
  • タスクの削除

ソースはgithubにあげてあるので、よかったら参考にしてください。

github
https://github.com/megaya/vue_firebase_todo

config/local.env
'use strict'
module.exports = {
  FIRE_BASE: {
    API_KEY: '"......"',
    AUTH_DOMAIN: '"......"',
    DATABASE_URL: '"......"',
    PROJECT_ID: '"......"',
    STORAGE_BUCKET: '"......"',
    MESSAGING_SENDERID: '"......"',
  }
}

一応パブリックにあげてあるのでconfig/locale.envというファイルにfirebaseの設定を書くようにした。もしgithubに上がっているものを使う場合は、上記の内容でファイルを作成して、.....部分は自分のFirebaseの値をいれてください。

Vue.jsの環境構築

$ npm install --global vue-cli
$ vue init webpack todo

vue-cliという便利なものがあるので、コマンド一発で簡単にインストールできる。色々と環境設定を選択する。僕はyarnを選んでいるけど、npmでも問題ない。

$ yarn add firebase --save

インストールが終わったらfirebaseのライブラリも追加しておく。

ソースの說明

触るのはsrc/main.jssrc/App.vueのみ。まずはmain.jsの方から見ていく。

src/main.js

src/main.js
import Vue from 'vue'
import App from './App'
import firebase from 'firebase'

Vue.config.productionTip = false

console.log(process.env.FIRE_BASE);

// Initialize Firebase
var config = {
        apiKey: process.env.FIRE_BASE.API_KEY,
        authDomain: process.env.AUTH_DOMAIN,
        databaseURL: process.env.FIRE_BASE.DATABASE_URL,
        projectId: process.env.FIRE_BASE.PROJECT_ID,
        storageBucket: process.env.STORAGE_BUCKET,
        messagingSenderId: process.env.MESSAGING_SENDERID,
};
firebase.initializeApp(config);

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
})

特に特殊なことはやっていなくって、Firebaseの設定ファイルを読み込んでいるだけ。config/local.envに書かれた変数を、config/dev.env.jsで読み込んでいる。

vue-todo_–_概要_–_Firebase_console.png

公式トップページ
https://firebase.google.com/?hl=ja

FireBaseの設定値はプロジェクトを作ってすぐのページの「ウェブアプリにFirebaseを追加」を押すと表示されるので、その値をそのまま入れる。

src/App.vue

src/App.vue
<template>
  <div id="app">
    <h2>タスク</h2>
    <div>
      <input type="text" v-model="newTodoName">
      <button type="submit" v-on:click="createTodo()">タスク作成</button>
    </div>
    <ul>
      <li><button type="submit" v-on:click="showTodoType = 'all'">すべて</button></li>
      <li><button type="submit" v-on:click="showTodoType = 'active'">未完タスク一覧</button></li>
      <li><button type="submit" v-on:click="showTodoType = 'complete'">完了タスク一覧</button></li>
    </ul>
    <ul v-for="(todo, key) in filteredTodos">
      <li><input class="toggle" type="checkbox" v-model="todo.isComplete" v-on:click="updateIsCompleteTodo(todo, key)">{{ todo.name }}</li>
      <button type="submit" v-on:click="deleteTodo(key)">削除</button>
    </ul>
  </div>
</template>

<script>
import firebase from 'firebase'

export default {
  name: 'App',
  created: function() {
    this.database = firebase.database();
    this.todosRef = this.database.ref('todos');

    var _this = this;
    this.todosRef.on('value', function(snapshot) {
      _this.todos = snapshot.val(); // データに変化が起きたときに再取得する
    });
  },
  computed: {
    filteredTodos: function () {
      if (this.showTodoType == 'all') {
        return this.todos;
      } else {
        var showIsComplete = false;
        if (this.showTodoType == 'complete') { showIsComplete = true }
        var filterTodos = {};
        for (var key in this.todos) {
          var todo = this.todos[key];
          if (todo.isComplete == showIsComplete) { filterTodos[key] = todo; }
        }
        return filterTodos;
      }
    }
  },
  methods: {
    createTodo: function() {
      if (this.newTodoName == "") { return; }
      this.todosRef.push({
        name: this.newTodoName,
        isComplete: false,
      })
      this.newTodoName = "";
    },
    updateIsCompleteTodo: function (todo, key) {
      todo.isComplete = !todo.isComplete
      var updates = {};
      updates['/todos/' + key] = todo;
      this.database.ref().update(updates);
    },
    deleteTodo: function (key) {
      this.database.ref('todos').child(key).remove();
    },
  },

  data () {
    return {
      database: null,
      todosRef: null,
      newTodoName: '',
      showTodoType: 'all',
      todos: []
    }
  }
}
</script>

<style>
</style>

データベースから値の取得

  created: function() {
    this.database = firebase.database();
    this.todosRef = this.database.ref('todos');

    var _this = this;
    this.todosRef.on('value', function(snapshot) {
      _this.todos = snapshot.val();
    });
  },

Vueはページが開かれたときにcreatedが呼ばれるようになっているので、まずはここでFirebaseからタスクの一覧を取得するようにする。

Firebaseではthis.database().ref('todos')というように、refにパスを指定して値を取得する。todos以下のオブジェクトがすべて取得できる。

this.todosRef.on('value', ...でイベントを設定しておくと、todos以下のデータに変化が起きたときにコードが実行される。ここでは_this.todos = snapshot.val();が実行される。これでタスクを追加したり、更新したり、削除されたりすると、常にthis.todosに最新のデータがロードされるようになる。

ウェブでのデータの取得(公式ページ)
https://firebase.google.com/docs/database/web/retrieve-data?hl=ja

タスクの追加

<input type="text" v-model="newTodoName">
<button type="submit" v-on:click="createTodo()">タスク作成</button>
    createTodo: function() {
      if (this.newTodoName == "") { return; }
      this.todosRef.push({
        name: this.newTodoName,
        isComplete: false,
      })
      this.newTodoName = "";
    },

タスクを作成するテキストボックスの部分。isCompletehという値はタスクが完了しているかどうかを判断するためのフラグ。

createdで生成したtodoRefに値を渡す。FirebaseのデータベースはNoSQLなので、jsonで楽にデータを入れていける。migrationとかを設定しなくてもサクサクっとできるので、プロトタイプのサイトとかを試しに作るのとかにも向いていると思う。(本格的にやるなら設計が大変だけど)

タスク一覧の表示

    <ul v-for="(todo, key) in filteredTodos">
      <li><input class="toggle" type="checkbox" v-model="todo.isComplete" v-on:click="updateIsCompleteTodo(todo, key)">{{ todo.name }}</li>
      <button type="submit" v-on:click="deleteTodo(key)">削除</button>
    </ul>

createdで取得したtodosをv-forでループして回す。

{ xxxxx: {name: 'hoge', isComplete: false}, yyyyyy: {name: 'hoge', isComplete: false}}

Firebaseで取得した値は配列ではなく、上記のようにオブジェクトでくる。なので<ul v-for="(todo, key) in filteredTodos">のように、オブジェクトのkeyも一緒にループして取得するようにしている。ちなみにオブジェクトのキーはFirebase側で勝手に作ってくれるので、こちらでデータベースのプライマリーキーのようなものを意識して作る必要は一切ない。

    <ul>
      <li><button type="submit" v-on:click="showTodoType = 'all'">すべて</button></li>
      <li><button type="submit" v-on:click="showTodoType = 'active'">未完タスク一覧</button></li>
      <li><button type="submit" v-on:click="showTodoType = 'complete'">完了タスク一覧</button></li>
    </ul> 
  computed: {
    filteredTodos: function () {
      if (this.showTodoType == 'all') {
        return this.todos;
      } else {
        var showIsComplete = false;
        if (this.showTodoType == 'complete') { showIsComplete = true }
        var filterTodos = {};
        for (var key in this.todos) {
          var todo = this.todos[key];
          if (todo.isComplete == showIsComplete) { filterTodos[key] = todo; }
        }
        return filterTodos;
      }
    }

さっき書いたようにオブジェクトでループを回しているので、タスクを並び替えるときはArray.filtterが使えない。オブジェクトをループして配列を作り直すようにしている。

createdのときの取得時点でオブジェクトを配列に入れ直した方がいいのかもしれない。このあたりもっといい方法があれば教えてください…

ちなみにfilteredTodosというのをcomputedに定義しているのは値がキャッシュされるから。methodsに定義しても同じように動作するけど、そのキャッシュ周りの動作が微妙に違うので、詳しくは下記参照。

https://jp.vuejs.org/v2/guide/computed.html

タスクの削除

<button type="submit" v-on:click="deleteTodo(key)">削除</button>
    deleteTodo: function (key) {
      this.database.ref('todos').child(key).remove();
    },

ここは単純にループしたkeyをもとにして削除している。ただしもっとしっかり作るなら、コールバックでしっかりエラー処理をしておくべき。(手抜き)

タスクの未完了/完了の変更(アップデート)

<li><input class="toggle" type="checkbox" v-model="todo.isComplete" v-on:click="updateIsCompleteTodo(todo, key)">{{ todo.name }}</li>
    updateIsCompleteTodo: function (todo, key) {
      todo.isComplete = !todo.isComplete
      var updates = {};
      updates['/todos/' + key] = todo;
      this.database.ref().update(updates);
    },

updatesというオブジェクトにパスを入れてアップデートする。updatesに複数のkeyを入れれば一斉にアップデートもできる。

ちなみにkeyを指定して、{ isComplete: falase }だけのオブジェクトを作って投げると、nameカラムが消滅するので、オブジェクトをアップデートするときは、値をすべてしっかり入れるようにする。

Firebase便利!

小さいアプリとかモック作りたいときはものすごく便利だと思う。カラムの変更が難しいので、大規模だとかなり設計をしっかりしないといけないとも感じた。

APIを自分で作らなくていいってまじで楽だ…

参考

FirebaseのRealtime Databaseをざっくり使ってみる ~導入から取得編(保存に関しては編集中)~
https://qiita.com/seiya1121/items/4cbc32678558315c2159

TodoMVC の例(Vue.jsの公式サイト)
https://jp.vuejs.org/v2/examples/todomvc.html

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした