普段私はNuxt.jsを使っているのですが、今までのVue2時には、(他の選択肢を知らずに)Vuexで認証情報を管理していました。
しかし、Vue3ではprovide/injectを使ったステート管理が中々推されており、業務の中でこの度チャレンジしました。
そして『Nuxt.js + Composition API(provide/inject) + Firebaseでの最低限の認証機能を実装した』という記事は現状まったく無かったため、この度自分で記事を書きます。
(※)この記事では、そもそもの『Firebase Authenticationの使い方』や『Nuxtの使い方』については言及しません。
(※)provide/injectのメリットについては、この方の記事が個人的に中々わかりやすかったです。
Vue.2までだと、『1つの.vueファイルの中にテンプレート/ステート/ロジックを全て書く必要があったのが、今後はそれらを別のファイルに書き分けられる』という事です。
https://qiita.com/karamage/items/4bc90f637487d3fcecf0
firebaseの情報はpluginで管理します。のちにimportします。
import * as firebase from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'
import 'firebase/functions'
import * as firebase_secret from '../_secret/firebase' //ここにsecrets情報を入れている
firebase.initializeApp(firebase_secret)
export const auth = firebase.auth()
export const functions = firebase.app().functions('us-central1') //('asia-northeast1')
export const db = firebase.firestore()
export const timestamp = () => firebase.firestore.FieldValue.serverTimestamp()
また、殆どの方はFirebase Authenticationで認証を行った後、Cluoud Firestoreから等ユーザーの情報を引っ張ってきたいでしょう。
Cloud Firestoreでは、
/{user collection}/${userID}/フィールド(nameなど)というシンプルな構成をここでは想定します。
そしてNuxt.のルートディレクトリに/composablesディレクトリを作成し、その中に**①store情報を使うための鍵のファイル(user-store-key.ts)と②ストア本体のファイル(user-store.ts)**を作ります。
import { InjectionKey } from '@vue/composition-api';
import { UserStoreType } from './user-store';
const UserStoreKey: InjectionKey<UserStoreType> = Symbol('UserStore');
export default UserStoreKey;
import { reactive, ref } from "@vue/composition-api"
import { auth, db, timestamp } from '../plugins/firebase'
export default function UserStore() {
const state = reactive({
id: "",
email: "",
name: "",
})
const GetUserStore =()=> state;
const SetUserStore = (user: firebase.User) => {
console.log("Login Information", user)
const ref_userinfo = db.collection('users').doc(user.uid)
ref_userinfo.onSnapshot(doc => {
if (doc.exists) {
console.log("Obtained User contact_info from DB", doc.data()!.contact_info)
const parsedUserInfo = {
id: doc.id,
email: user.email!,
name: doc.data()!.name,
}
state = parsedUserInfo
console.log("Reflected on state", GetUserStore())
} else {
console.log("No User Data")
}
});
}
}
return { SetUserStore, GetUserStore}
}
export type UserStoreType = ReturnType<typeof UserStore>
コレで最低限のストアの構築は完成です!
SetUserStore()が呼ばれた際、stateのidとemailはAuthenticationの情報をセットし、nameはFirestoreから引っ張ってきます。
また、ゲッターとしてGetUserStore()、セッターとしてSetUserStore()を定義しreturnする事で、stateに外部から直接アクセス出来ないようにする事で保守性を上げています。
次に、/layout/default.vueにて、SetUserStore()関数をonMounted()のタイミングで実行するようにします。
stateの内容は、ページをリロードされると消えてしまいますが、layoutsで定義してしまえば、ページをリロードされる度にSetUserStore()関数が呼ばれ、Firebaseから値を引っ張ってきます。
<template>
<v-app >
<v-main>
<v-container class="pa-0">
<nuxt />
</v-container>
</v-main>
</v-app>
</template>
<script lang="ts">
import Vue from 'vue'
import { auth, db, timestamp } from '../plugins/firebase'
import { defineComponent, SetupContext, onMounted, provide, inject } from '@vue/composition-api'
import UserStore from "../composables/user-store"
import UserStoreKey from "../composables/user-store-key"
export default defineComponent({
setup(props, context) {
provide(UserStoreKey, UserStore()) //ここでprovideを使用
const store = inject(UserStoreKey) as UserStoreType //ここでinjectを使用
onMounted(() => {
auth.onAuthStateChanged((user) => {
if (user) {
store.SetUserStore(user)
} else {
console.log("Not Authenticated User")
}
})
})
return { store}
}
})
</script>
<style scoped>
</style>
公式ドキュメントを読むと、provideは送信側/injectは受信側を想定しているようですが、上記の通り、provideとinjectを同時に使う事も問題ありません。
また、(Vue2の時は)この処理はmiddlewareに書いても動作しましたが、Vue3ではmiddlewareに書いても動作しません!!
何故なら、provide/inject関数はsetupコンテキストの中でしか動作しないためです。
終わりに
この度、初めてprovide/injectを使用しましたが、Vuexと比べると
①storeのファイルが肥大化しづらい
②Vuexと違いグローバル変数では無いため、そこで保持すべきでない情報を持たずに済む。
③(②にも関連するが) どこでstoreが変更がされているかを追いやすく、大規模開発でも破綻しづらい
④型推論がイイ!!
というメリットを感じました。今後積極的に使ってゆこうと思います。