🏬

FirebaseV9の書き方でデータを取得してみる

2021/10/30に公開

この記事について

2021年8月25日に、Firebaseのバージョン9が配信されたことにより書き方が大きく変わりました。
この記事はv9へアップデートした際にリファクタリングした内容の備忘録です。
firebaseへのアクセスは、TypeScriptを併用してtsファイルに記載しています。

環境

  • Firebase 9.1.3
  • Nuxt.js 2.15.7
  • TypeScript 4.2.4
  • macOS Big Sur 11.5.2

前提

v9を使う理由

firebaseのv9はモジュールバンドラを利用しています。
v9ではファイルサイズの縮小化を図り、必要なものしかインポートしない形式に変更されました。
これにより、ページの描画速度が早くなります。

v9以前の書き方で9へアップデートするとエラーが起こってしまう。

何故このようなエラーが出てしまうのかと言うと、関数ベースでの書き方に変更する必要があるからです。(v8まではクラスベースで記述を行っています。)
具体的には、firebase.firestore...の部分がエラーになります。

とりあえず解決する方法:compatを利用する

いきなり全てをv9に変えるのは大変だ!!というユーザの為に、firebaseはv8と互換性を持たせる方法も用意してくれています。
互換性を持たせるには、インポートしている部分を下記の様に書き換えます。

getEnableFirebaseData/index.ts
// firebase/appをインポートしている部分を
import firebase from "firebase/app";

// compatを追加する
import firebase from "firebase/compat/app"

とりあえずエラーを無くしたい場合は、compatを追加することでv9を使いながらほぼコードを変える事なくエラーを解消することができます。
ただし、この方法は今後リリースされるバージョン(v10またはv11)にて完全に削除される予定であると公式からアナウンスが出ています。
あくまでv9への移行期間の為の救済処置の様なものなので、firebaseを利用している場合は、移行期間が終わる前までにリファクタリングが必要になります。
もしこれでもエラーが出ていてどうしようもない場合は、firebaseのバージョンを8に戻す事でエラーは消えます。(根本的な解決にはなりませんが、、)

v8からv9へのリファクタリング手順

ここから本題。まずは初期化をおこないます。
firebaseAppは色々なところで使う事が多いので、ファイルを切り出して共通で使う様にしています。

components/settings/firebase/index.ts
import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: process.env.apiKey,
  authDomain: process.env.authDomain,
  databaseURL: process.env.authDomain,
  projectId: process.env.projectId
};

const firebaseApp = initializeApp(firebaseConfig);

export { firebaseApp };

exportすることで、別ファイルでfrebaseAppを使えるようにします。

全件取得してみる

v8での書き方

getEnableFirebaseData/index.ts
import firebase from "firebase/app";
import { EnableHogeType } from "types/GetFirebaseDataTypes";
const TARGET_COLLECTION_NAME = "collection_Firebase_name";

//全て取得
export default function(
  db: firebase.firestore.Firestore
): Promise<EnableHogeType[]> {
  return new Promise((resolve, reject) => {
    const collectionRef = db.collection(TARGET_COLLECTION_NAME);
    collectionRef
      .get()
      .then(function(querySnapshot: firebase.firestore.QuerySnapshot) {
        const ret: EnableHogeType[] = [];
        querySnapshot.forEach(
          (doc: firebase.firestore.QueryDocumentSnapshot) => {
            ret.push(doc.data() as EnableHogeType);
          }
        );
        resolve(ret);
      })
      .catch(function(error) {
        reject(error);
      });
  });
}

V9の書き方

getEnableFirebaseData/index.ts
import {
  collection,
  getDocs,
  getFirestore,
  query
} from "firebase/firestore/lite";
import { EnableHogeType } from "types/GetFirebaseDataTypes";
import { firebaseApp } from "~/components/settings/firebase";

const TARGET_COLLECTION_NAME = "collection_Firebase_name";

export default async function() {
  try {
    const db = getFirestore(firebaseApp);
    const q = query(collection(db, TARGET_COLLECTION_NAME));
    const querySnapshot = await getDocs(q);
    const ret: EnableHogeType[] = [];
    querySnapshot.forEach(doc => {
      ret.push(doc.data() as EnableHogeType);
    });
    return ret;
  } catch (error) {
    // 何もしない
  }
}

コード量が減り、全体的に見通しが良くなりました。
v9の特徴である必要なメソッドのみインポートしているのはfirebase/firestore/liteです。
v8だと、import firebase from "firebase/app"としていて、使わないメソッドまでインポートしていたのですが、v9ではgetDocsやqueryなど必要なものだけをインポートしているので、結果的にバンドルサイズが縮小されるという事です。

import {
  collection,
  getDocs,
  getFirestore,
  query
} from "firebase/firestore/lite";
import { firebaseApp } from "~/components/settings/firebase";

データの取得方法ですが、コレクションの指定はqueryメソッドを使い、dbを第1引数、コレクション名を第2引数に指定します。

const q = query(collection(db, TARGET_COLLECTION_NAME));

firebaseからデータを取得している部分は、getDocsメソッドです。非同期処理は公式の書き方を参考にしてasync・awaitを使用しました。
また、公式のドキュメントには載ってなかったのですが、エラーハンドリングはtry,catchで処理を行うように追記しています。

export default async function() {
  try {
    const db = getFirestore(firebaseApp);
    const q = query(collection(db, TARGET_COLLECTION_NAME));
    const querySnapshot = await getDocs(q);
    const ret: EnableHogeType[] = [];
    querySnapshot.forEach(doc => {
      ret.push(doc.data() as EnableHogeType);
    });
    return ret;
  } catch (error) {
    // 何もしない
  }
}

取得した情報は、returnで返すことで別のファイルでこの関数を実行すれば取得することができます。
上記では、空の配列retを用意してreturn retで返しています。

利用するとき

例えば、Nuxt.jsの場合はこの様にインポートして関数を実行すればOK。
この辺はv8と変わらないです。

vueファイル
import getFirebase from "~/getEnableFirebaseData" // firebaseのfileをインポート
<script>
 methods: {
   getEnable: function(){
     getFirebase()
      .then(list => {
	 // firebaseで取得した情報が入ってくる
      })
      .catch(e => {
	 // エラーの際の処理
      })
   }
 }
</script>

こんな感じで書き換えてfirebaseからデータを取得することができました。
v9でも、勿論where条件など細かい要件に合わせたメソッドも用意されていて、上記と同じ様に記述をする事でリファクタリングができます。whereだとこんな感じ。

getEnableFirebaseData/index.ts
import { collection, query, where, getDocs } from "firebase/firestore";

const q = query(collection(db, TARGET_COLLECTION_NAME), where("name", "==", true));

該当するidのサブコレクションからデータを取得

v8での書き方

getEnableFirebaseData/index.ts
import firebase from "firebase/app";
import { HogehogeType } from "types/GetFirebaseDataTypes";

export default function(
  db: firebase.firestore.Firestore,
  clientId: string
): Promise<HogehogeType | {}> {
  return new Promise((resolve, reject) => {
    if (clientId) {
      const collectionRef = db
        .collection(TARGET_COLLECTION_NAME)
        .doc(clientId)
        .collection(SUB_COLLECTION_NAME);
      collectionRef
        .orderBy("updateAt", "desc")
        .limit(1)
        .get()
        .then(function(querySnapshot: firebase.firestore.QuerySnapshot) {
          let ret: HogehogeType | {} = {};
          querySnapshot.forEach(
            (doc: firebase.firestore.QueryDocumentSnapshot) => {
              ret = doc.data() as HogehogeType;
            }
          );
          resolve(ret);
        })
        .catch(function(error) {
          reject(error);
        });
    } else {
      resolve({});
    }
  });
}

該当するコレクションの中から(上記ではidとしています)、サブコレクションにあるデータの一番新しいものを一つ取得しています。

V9の書き方

getEnableFirebaseData/index.ts
import {
  collection,
  doc,
  getDocs,
  getFirestore,
  limit,
  orderBy,
  query
} from "firebase/firestore/lite";
import { HogehogeType } from "types/GetFirebaseDataTypes";
import { firebaseApp } from "~/components/settings/firebase";

const TARGET_COLLECTION_NAME = "hogegogeCollection";
const SUB_COLLECTION_NAME = "historyCollection";
const db = getFirestore(firebaseApp);

export default async function(clientId: string) {
  try {
    if (clientId) {
      const docRef = doc(db, TARGET_COLLECTION_NAME, clientId);
      const q = query(
        collection(docRef, SUB_COLLECTION_NAME),
        orderBy("updateAt", "desc"),
        limit(1)
      );

      const querySnapshot = await getDocs(q);
      let ret: HogehogeType | {} = {};
      querySnapshot.forEach(doc => {
        ret = doc.data() as HogehogeType;
      });
      return ret;
    }
  } catch (error) {
    // 何もしない
  }
}

サブコレクションを指定するときは一度指定のコレクション名を指定して、その上でqueryメソッドで取得をしています。

getEnableFirebaseData/index.ts
const docRef = doc(db, TARGET_COLLECTION_NAME, clientId);

最後に

クラスベースから関数ベースへと変わったことにより、書き方も変わって最初は戸惑いましたが、慣れるとコード量も減って読みやすくなりました。
まだバージョンが変わって日が経過していない為なのか、公式ドキュメントの参考のコードがもっと充実してくれると実装しやすくなるかも?と思いました。

参考資料

公式のドキュメントにはv9のサンプルコードがいくつか掲載されています。
https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja#web-version-9_1

Discussion