Firebase Javascript SDK v8→v9 のCloud FireStore取扱書 in Nuxt × TypeScript

# はじめに
Firebase Javascript SDK v8とv9では記法に互換性がなくなってしまいました。
そのため、素直にv8 SDK→v9 SDKとアップデートするだけでは動かなくなってしまったのでv8, v9それぞれの記法の違いを簡単にまとめようと思います。
その際に遭遇した型エラーもこちらへ残します。

※ 今回はCloud FireStoreに触れます

# 対象者
- Nuxt.jsでFirebaseのFireStoreを利用している方
- Firebase Javascript SDK v8 → v9にアップデートしたい方
- FireStoreに型を付けたい方
- 🐭🐮🐯🐰🐲🐍🐴🐏🐵🐔🐶🐗

# 試した環境
- Node.js (14.18.0）
- yarn (1.22.4)
- Firebase （9.1.3）
- TypeScript (4.2.4)
- VScode（1.60.0）
- Nuxtは2系です

# まずはFirebase初期化部分から

## v8 SDK
Firebase JavaScript SDK v8の詳しい導入方法は[こちら](https://firebase.google.com/docs/web/alt-setup?hl=ja)に記載されていますが、公式が「v9 SDK を使用することを強くおすすめします。」と言っているので特別な理由がない限りは、v9 SDKを利用した方がいいみたいです。


```plugins/firebase.ts
// v8
import firebase from 'firebase/app';

if (!firebase.apps.length) {
  firebase.initializeApp({/* config */});
}

export default firebase;


```

上記補足として、以下のエラーが発生することがあったので

```
Firebase: Firebase App named '[DEFAULT]' already exists
```

対処として

```plugins/firebase.ts
if (!firebase.apps.length){ 
  ...
}
```

で囲ってエラー回避していました。



## v9 SDK
import 周りが変わっています。
利用するやつのみ import することによってコンパイル時のアプリサイズを削減できるのが売りらしいです。

[当時のリリースノート](https://firebase.google.com/support/releases#august_27_2021
)
> 📦新しいモジュラーJavaScriptSDKは、アプリバンドルを最大80％小さくすることができますJavaScript SDKのバージョン9は、バンドラーが未使用のコードを排除し、JavaScriptバンドルのFirebaseライブラリコードを最大80％削減できるモジュールファーストフォーマットを採用しています。

configに型が用意されていたので早速使っています。

```plugins/firebase.ts
// 必要なもののみimport
import { FirebaseOptions, getApps, getApp, initializeApp, FirebaseApp } from 'firebase/app';

const firebaseConfig: FirebaseOptions = {/* config */};

const firebase: FirebaseApp = !getApps().length ? initializeApp(firebaseConfig) : getApp();

export default firebase;
```

v8の時に発生したエラーはv9では `!getApps().length` を利用した分岐で回避するようにしました。

# Cloud FireStoreを利用する

[公式ドキュメント](https://firebase.google.com/docs/firestore/quickstart?hl=ja#web-version-8_4)

ここでは、データ取得, データ追加周り v8 と v9 の時の記法をそれぞれご紹介します。
必要な部分のみ抜粋しています。

## データ取得
はじめにデータ取得の実装周りです。

取得部分は今回 `created` 内に書いていますが、あくまで記事用なので `created` 内に書く特別な意図はありません。

### v8 SDK
firebase/firestore はまるまるimportしています。

```vuejs
// v8
<script lang="ts">
import firebase from '~/plugins/firebase';
import 'firebase/firestore';

export default Vue.extend({
  created () {
    const db: firebase.firestore.Firestore = firebase.firestore(); // Firestore のインスタンスを初期化

    if (!db) { return; }
    db.collection('hoge').get()
      .then((querySnapshot) => {
        querySnapshot.forEach((doc: firebase.firestore.QueryDocumentSnapshot) => {
         ...
        });
      });
  }
})
```

### v9 SDK
firestoreは必要な部分のみimportするようになりました。
記法が変わり、 `getDocs` という取得用の新しい関数が登場しました。
また、型定義も変わっています。

```vuejs
// v9
<script lang="ts">
import firebaseApp from '~/plugins/firebase';
import { Firestore, getFirestore, getDocs, collection, QueryDocumentSnapshot } from 'firebase/firestore';

export default Vue.extend({
  created () {
    const db: Firestore = getFirestore(firebaseApp); // Firestore のインスタンスを初期化

    if (!db) { return; }
    getDocs(collection(db, 'hoge'))
      .then((querySnapshot) => {
        querySnapshot.forEach((doc: QueryDocumentSnapshot) => {
        ...
        });
      });
  }
})
```

## データ追加
続いてデータ追加の実装周りです。

### v8 SDK

```vuejs
// v8
<script lang="ts">
import firebaseApp from '~/plugins/firebase';
import 'firebase/firestore';

export default Vue.extend({
  methods: {
    addData () {
      const db: Firestore = getFirestore(firebaseApp);
      db.collection("hoge").add({
          name: "deren",
          country: "Japan"
      });
    }
  }
})
```

### v9 SDK
こちらも記法が変わり、 `addDoc` というデータ追加用の新しい関数が登場しました。

```vuejs
// v9
<script lang="ts">
import firebaseApp from '~/plugins/firebase';
import { Firestore, getFirestore, addDoc, collection } from 'firebase/firestore';

export default Vue.extend({
  methods: {
    addData () {
      const db: Firestore = getFirestore(firebaseApp);
      await addDoc(collection(db, 'hoge'), {
        name: 'deren',
        country: 'Japan'
      });
    }
  }
})
```

# v8 SDK → v9 SDKにアップデートした際の型エラー

互換性がなくなった影響で、v8 SDK → v9 SDK へアップデートした際に型エラーがちらほら発生しました。
以下に記載する私が遭遇したエラーはこの記事で解消できるものなので、似たようなエラーに遭遇してしまったらこの記事を参考にしていただけると嬉しいです。

## Cannot find namespace 'firebase'

![スクリーンショット 2021-11-11 20.13.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302304/38dda9fe-fe7e-29bf-7f40-80251a21ef69.png)


```js
// v8
const db: firebase.firestore.Firestore = firebase.firestore(); 

// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

// v9
const db: Firestore = getFirestore(firebaseApp);

```
※ 型は `firebase/firestore` から importしてください

## Property 'firestore' does not exist on type 'FirebaseApp'

![スクリーンショット 2021-11-11 20.15.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302304/dc60008c-bd27-5e2a-9d7e-93139f091b45.png)


```js
// v8
const db: firebase.firestore.Firestore = firebase.firestore(); 

// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

// v9
const db: Firestore = getFirestore(firebaseApp);
```
※ 型は `firebase/firestore` から importしてください

## Parameter 'querySnapshot' implicitly has an 'any' type.
![スクリーンショット 2021-11-11 20.20.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302304/d41585f3-d5e5-03cd-97f7-3b012efb3482.png)

```js
// v8
      await this.db.collection('hoge').get()
        .then((querySnapshot) => {
          querySnapshot.forEach((doc: firebase.firestore.QueryDocumentSnapshot) => {
            ...
          });
        })

// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

// v9
      await getDocs(collection(this.db, 'hoge'))
        .then((querySnapshot) => {
          querySnapshot.forEach((doc: QueryDocumentSnapshot) => {
            ...
          });
        })
```
※ 型は `firebase/firestore` から importしてください

# 参考
- [Firebase を JavaScript プロジェクトに追加する](https://firebase.google.com/docs/web/setup?hl=ja)
- [Cloud Firestore を使ってみる](https://firebase.google.com/docs/firestore/quickstart?hl=ja)