Edited at

Nuxt.js と Firebase(Firestore)を使って認証と DB 保存を実装する

この記事は Nuxt.js #2 Advent Calendar 2018 の 20日目の記事です。

20日目の記事ですが、いま投稿しているのは 21日目です。(スミマセン💦)

今回の記事では、このアドベントカレンダーのためだけに作った謎ゲーム、

「Nuxt の戦闘力をあげる」で使った技術についてご紹介します。

https://growing-trees.firebaseapp.com/

GitHub のリポジトリはこちらです。

https://github.com/ryamakuchi/growing-trees


TL;DR


  • Nuxt.js で作成した静的サイトを Firebase にホスティングする

  • Vuex で状態管理する

  • Google 認証機能と Firestore 連携機能は Nuxt.js で書くと1日で実装できる


どんなものが出来上がったか


最初の想定

本当は、アビスリウムみたいなタップして木を育てる育成ゲームが作りたかったです。

(今回は時間がなかったのでサンプル程度のアプリケーションとなりました)


1日で作れた範囲

Nuxt.mov.gif

出来上がったサンプルは、


  • Firebase Authentication を使って Google アカウントでログインする

  • クリックすると戦闘力がカウントされ Firestore に保存される

  • ログアウトしてからまたログインすると戦闘力が保持されている

  • 「はじめから育てる」を押して戦闘力をリセットできる

といった部分が実装されています。


実装について

まずは普通に Nuxt.js のアプリケーションを Firebase にホスティングさせます。

ホスティングするまでの流れについては Nuxt.js x Firebase 事始めがとても参考になったので、そちらをどうぞ。

ちなみに CSS は Tailwind CSS を使ってみました。

Nuxt.js を使っていますが SSR はしておらず単純な SPA 構成となっており、

ログインしているかどうかでコンポーネントを出し分けています。

また、今回のリポジトリでは API キーなどの情報が入っている src/plugins/firebase.js を git ignore しています。

(JS で使われる API で、API キーが見えてしまうのは仕方がないとは思うのですが、、気休め程度にはなるかと思い)

https://teratail.com/questions/48603

firebase.js の中身はこのようなコードです。


src/plugins/firebase.js

import firebase from 'firebase'

if (!firebase.apps.length) {
firebase.initializeApp({
apiKey: "XXXXXXXXXXXXXXXXXXXXXXXX",
authDomain: "XXXXXXXXXX.firebaseapp.com",
databaseURL: "https://XXXXXXXXXX.firebaseio.com",
projectId: "XXXXXXXXXX",
storageBucket: "XXXXXXXXXX.appspot.com",
messagingSenderId: "XXXXXXXXXX"
})
}

const db = firebase.firestore()
const settings = { timestampsInSnapshots: true }
db.settings(settings)
export { db }



Vuex で戦闘力を上げる流れの説明

今回作ったアプリケーションは、認証と Firestore への連携をすべて Vuex を使って行いました。


state で値を定義する

まずは state で状態管理したいものの値を定義します。

counter はクリックした数をカウントする値(戦闘力)です。

https://github.com/ryamakuchi/growing-trees/blob/master/src/store/index.js


src/store/index.js

export const state = () => ({

counter: 0,
userEmail: null,
userName: null,
userPhoto: null,
noAccount: false
})


コンポーネントで store の値を出力する

{{ $store.state.counter }} というように書くと state の値を出力できます。

https://github.com/ryamakuchi/growing-trees/blob/master/src/components/MyTree.vue


src/components/MyTree.vue

<template>

<div class="max-w-full max-h-full my-8 p-12 rounded overflow-hidden shadow-lg">
<div class="flex justify-center">
<button @click="increment()" class="bounce">
<Logo/>
</button>
</div>
<div class="mt-8 px-2 py-4 text-center">
<div class="font-bold sm:text-lg lg:text-2xl my-4">クリックして Nuxt を育てよう</div>
<div class="m-8 sm:text-lg lg:text-2xl">
戦闘力 {{ $store.state.counter }}
</div>
</div>
<button @click="reset()"
class="bg-orange lg:text-2xl hover:bg-orange-dark text-white font-bold py-2 px-4 rounded">
はじめから育てる
</button>
</div>
</template>


クリックされたら dispatch で actions の処理を呼ぶ

actions の処理を呼ぶときは dispatch を使います。


src/components/MyTree.vue

<template>

...
<button @click="increment()" class="bounce">
...
</template>

<script>
...

export default {
...
methods: {
increment () {
this.$store.dispatch('increment')
},
...
}
}
</script>



actions で Firestore 側の値を更新して mutations に commit する

{ commit, state } と書くことで、commit と state を束縛することができます。

https://vuex.vuejs.org/ja/guide/actions.html

state.counter + 1 の値を Firestore に格納し、格納が成功したら

commit('increment') で mutations に commit しています。


src/store/index.js

export const actions = {

...
increment ({ commit, state }) {
db.collection('users').doc(state.userEmail).update({
counter: state.counter + 1
}).then(() => {
console.log("Document successfully updated!")
commit('increment')
}).catch((error) => {
console.error("Error updating document: ", error)
})
},
...
}


mutations で state の情報を更新する

state の情報を更新できるのは mutations だけなので、最終的にはここで状態を変更します。


src/store/index.js

export const mutations = {

increment (state) {
state.counter++
},
...
}

これで一通り Vuex の流れが分かりました!

こうして見るとこの図の流れの通りで分かりやすいですね。

vuex.png


Google アカウントを使った認証と Firestore へのデータ保存について

今回は Firebase Authentication を使って認証しましたが、思っていたよりもとても簡単に認証ができました。

認証するまでの流れについては以下の記事が分かりやすかったです。

Nuxt.js と Firebase Authentication でログイン認証を作る

本当は Twitter アカウントを使った認証にしたかったのですが、Twitter developer account を取得するのに時間がかかりそうで1日ではできそうになかったので、Google アカウント認証にしました。

認証ロジックとしては、公式リファレンスのとおりにやっていく感じです。

https://firebase.google.com/docs/auth/web/manage-users?hl=ja



...

export const actions = {
// ログインするときの処理
googleSignIn ({ dispatch }) {
firebase.auth().signInWithRedirect(new firebase.auth.GoogleAuthProvider())
// ログインしているかどうかのチェック
dispatch('googleAuthStateChanged')
},

// ログアウトするときの処理
googleSignOut ({ dispatch }) {
firebase.auth().signOut()
// ログインしているかどうかのチェック
dispatch('googleAuthStateChanged')
},

// ログインしているかどうかチェックする処理
googleAuthStateChanged ({ dispatch, commit }) {

// 現在ログインしているユーザーを取得する
firebase.auth().onAuthStateChanged(user => {
// ログインしているユーザーがあったら
if (user) {
let { email, displayName, photoURL } = user
// 取得した情報を mutations に commit する
commit('storeUser', { userEmail: email, userName: displayName, userPhoto: photoURL })
// 初回ログインかどうかをチェックする処理
dispatch('userCheck')

// ログインしていなかったら mutations に commit する(ログインしていないときの画面を表示させる処理)
} else {
commit('deleteUser')
}
})
},

// 初回ログインかどうかをチェックする処理
userCheck ({ dispatch, commit, state }) {

// Firestore に ユーザー情報があるか
db.collection('users').doc(state.userEmail).get().then((doc) => {

// ユーザー情報があったら
if (doc.exists) {
console.log("Document data:", doc.data())
// counter の値を mutations に commit する
commit('saveUser', { number: doc.data().counter })

// ユーザー情報がなかったら
} else {
console.log("No such document!")
// ユーザー情報を Firestore に登録する処理
dispatch('createUser')
}

// エラーになったら
}).catch((error) => {
console.error("Error getting document:", error)
})
},

// ユーザー情報を Firestore に登録する処理
createUser ({ state }) {
db.collection('users').doc(state.userEmail).set({
counter: state.counter
}).then(() => {
console.log("Document successfully written!");
}).catch((error) => {
console.error("Error writing document: ", error);
})
},
...


正直このコードではエラー処理などを書いていないので色々と問題がありそうですが、

サンプルとしてあたたかい目で見ていただけるとうれしいです。


おわりに

今回このアプリケーションを作るにあたって、Vuex 周りなどを色々と教えてくださった @omiend さん本当に感謝です。

初のアドベントカレンダーでしたが、誰かのやっていきのお役に立てれば幸いです。