普段はRailsを使っているのだけど、webサービス作るときの選択肢を増やすためにFirebaseを使えるようにしておこうと思った。
僕はjsのフレームワークではVue.jsが一番好きなので、今回はVue+Firebaseで簡単なTodoアプリを作ってみた。
今回のTodoアプリでできることは以下の通り。ものすごくシンプルだし、見た目は気にしないでほしい。
- タスクの作成
- タスクの未完了/完了での表示切り替え
- タスクの削除
ソースはgithubにあげてあるので、よかったら参考にしてください。
github
https://github.com/megaya/vue_firebase_todo
'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.js
とsrc/App.vue
のみ。まずは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
で読み込んでいる。
公式トップページ
https://firebase.google.com/?hl=ja
FireBaseの設定値はプロジェクトを作ってすぐのページの「ウェブアプリにFirebaseを追加」を押すと表示されるので、その値をそのまま入れる。
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
に定義しても同じように動作するけど、そのキャッシュ周りの動作が微妙に違うので、詳しくは下記参照。
タスクの削除
<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