Firestoreのversion9における基本的な使い方

# はじめに
firestoreを初めて用いるために調べたところ、version8については多くの記述がありましたが、version9についてはほとんど見つかりませんでした。
そこで、version8のコードを独自に試行錯誤してversion9で利用する方法を簡単にまとめました。


# サンプルまとめ

![スクリーンショット 2023-06-28 14.56.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355468/0a47683e-ca26-383f-9d78-7d4dd9a382e6.png)
以下のサンプルではコレクションmenusと、それぞれ料理名などのフィールドを持ったドキュメントを取り扱います。


```sample.js
//コレクションの指定
collection(db, "menus");

/* データベースに追加 */
await addDoc(collection(db, "menus"), //第一引数(追加先コレクション)
    { //第二引数(フィールド群)
        name: "味噌汁", //料理名
        mealtime: "breakfast", //時間帯
        date: { //年月日
            year: 2023, 
            month: 5, 
            day: 6
        },
        createdAt: serverTimestamp(), //更新時刻
    });


/* データベースから読み込み */
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(doc => {
    //要素の処理
    console.log(doc.data()["name"]); //料理名取得
});


/* データベースから削除 */
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(async doc => {
    await deleteDoc(doc.ref); //ドキュメントの削除
});


/* ドキュメントの全体を更新 */
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(async doc => {
    await setDoc(doc.ref, { //全てのフィールドを更新する
        name: "豚汁", //料理名
        mealtime: "dinner", //時間帯
        date: { //年月日
            year: 2023,
            month: 7,
            day: 2
        },
        createdAt: serverTimestamp(), //更新時刻
    });
});


/* ドキュメントの一部を更新*/
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(async doc => {
    await updateDoc(doc.ref, { //一部のフィールドを更新
        "date.month": 7, //月 (ネストされたデータはドット'.'で区切る)
        mealtime: "dinner" //時間帯
        createdAt: serverTimestamp() //更新時刻
    });
});
```



# ドキュメントの生成・追加

## `addDoc()`
新規にドキュメントIDを生成して、指定したコレクションに追加します。
```sample.js
addDoc(第1引数, 第2引数);
```
- 第1引数: 追加先コレクション
- 第2引数: 追加する連想配列



```add1.js
/* データベースに追加 */

//1.コレクションを取得
const menus = collection(db, "menus");

//2.追加したいデータ構造を作成
const datArr = {
    name: "味噌汁", //料理名
    mealtime: "breakfast", //時間帯
    date: { //年月日
        year: 2023, 
        month: 5, 
        day: 6
    },
    createdAt: serverTimestamp() //更新時刻
}

//3.データベースに追加
await addDoc(menus, datArr);
```
簡略版 ↓
```add2.js
/* データベースに追加 */
await addDoc(collection(db, "menus"), {
    name: "味噌汁", //料理名
    mealtime: "breakfast", //時間帯
    date: { //年月日
        year: 2023, 
        month: 5, 
        day: 10
    },
    createdAt: serverTimestamp(), //記録した日時
});
```
追加例↓
![スクリーンショット 2023-06-28 14.56.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3355468/0a47683e-ca26-383f-9d78-7d4dd9a382e6.png)



# ドキュメントの取得
## `getDocs()`
指定したコレクションの中から複数のドキュメントを取得します。
```get1.js
//1.コレクション取得
const menus = collection(db, "menus");

//2.クエリ作成
const q = query(
    menus, //コレクション
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
);

//3.クエリで指定したドキュメント群取得
const docs = await getDocs(q);

//4.ドキュメント群から一つずつ取り出して処理
docs.forEach(doc => {
    console.log(doc.data()[name]);
});
```
### 1.コレクション取得
どのコレクションからドキュメントを取得するかを指定します。

### 2.クエリ作成
ここで取得したいドキュメントに応じて条件式を記入します。

```sample.js
query(第1引数, ...第2引数以降);
```
- 第一引数: 取得先コレクション
- 第二引数以降: 絞り込み条件、ソート条件


#### [絞り込み] `where(第一引数, 第二引数, 第三引数)`
- 第一引数: 比較するフィールド名
- 第二引数: 演算子 (等号など)
- 第三引数: 比較する値

```ex.js
//nameに"味噌汁"を持つドキュメントを取り出す
where("name", "==", "味噌汁")

//yearが2020年以降のドキュメントを取り出す
where("date.year", ">=", 2020)
```


#### [ソート] `orderBy(第一引数, 第二引数)`
- 第一引数: ソートのため参照する値
- 第二引数: 任意で`"desc"`を指定して降順にする

```ex.js
//ドキュメントの作成した記録を参照して古い順にソート
orderBy("createdAt", "desc") //タイムスタンプ降順で

//料理名を参照してアルファベット順(辞書順)にソート
orderBy("name")
```


### 3.クエリで指定したドキュメント群取得
変数docsに条件に当てはまった全てのドキュメントが入ります。
これは配列ではないため、`forEach()`で取り出す必要があります。

### 4.ドキュメント群から一つずつ取り出して処理
```.forEach()```を接続し、dogsから順番にデータを取り出して処理します。



### EX.簡略版
```get2.js
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(doc => {
    //要素の処理
    console.log(doc.data()["name"]); //料理名取得
});
```


# ドキュメントの削除
## `deleteDoc()`
指定したコレクションから指定したドキュメントを削除します。
```delete1.js
//1.コレクション取得
const menus = collection(db, "menus");

//2.クエリ作成
const q = query(
    menus, //コレクション
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
);

//3.クエリで指定したドキュメント群取得
const docs = await getDocs(q);

//4.ドキュメント群から一つずつ取り出して削除
docs.forEach(async doc => {
    await deleteDoc(doc.ref);　//ドキュメントの削除
});
```
流れは最後以外はコレクションの取得方法と同様です。

### 1.コレクション取得
どのコレクションからドキュメントを取得するかを指定します。

### 2.クエリ作成
ここで取得したいドキュメントに応じて条件式を記入します。


### 3.クエリで指定したドキュメント群取得
変数docsに条件に当てはまった全てのドキュメントが入ります。
これは配列ではないため、`forEach()`で取り出す必要があります。


### 4.ドキュメント群から一つずつ取り出して削除
`.forEach()`を接続し、dogsから順番にデータを取り出して削除します。
```ex.js
await deleteDoc(doc.ref);
```
`deleteDoc()`の引数に`doc.ref`を指定することでドキュメントを削除します。



### EX.簡略版
```delete2.js
//データベースから削除
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(async doc => {
    await deleteDoc(doc.ref); //ドキュメントの削除
});
```

# ドキュメントの更新
## `setDoc()`
指定したドキュメントの**全て**のフィールドを更新します。
```set.js
/* ドキュメントの全体を更新 */
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(async doc => {
    await setDoc(doc.ref, { //全てのフィールドを更新する
        name: "豚汁", //料理名
        mealtime: "dinner", //時間帯
        date: { //年月日
            year: 2023,
            month: 7,
            day: 2,
        },
        createdAt: serverTimestamp(), //更新時刻
    });
});
```
### ドキュメント群から一つずつ取り出して全体更新
```ex.js
setDoc(doc.ref, { 連想配列 });
```

- 第一引数: リファレンス
- 第二引数: 上書きする連想配列

ドキュメントが保持していた更新前の全てのフィールドを一度リセットして、指定したフィールドに上書きします。

```
//変更前
{
    name: "味噌汁",
    mealtime: "breakfast",
    date: {
        year: 2023, 
        month: 5, 
        day: 6,
    },
    createdAt: serverTimestamp(),
}

//変更後
{ 
    name: "豚汁", 
    mealtime: "dinner",
    date: {
        year: 2023,
        month: 7,
        day: 2,
    },
    createdAt: serverTimestamp(),
}
```


## `updateDoc()`
指定したドキュメントの**一部**のフィールドを更新します。
```update.js
/* ドキュメントの一部を更新*/
(await getDocs(query(
    collection(db, "menus"), //追加先コレクションの指定
    where("name", "==", "味噌汁"), //絞り込み
    orderBy("createdAt") //ソート
)))
.forEach(async doc => {
    await updateDoc(doc.ref, { 
        "date.month": 7, //月(ネストされたデータはドット'.'で区切る)
        mealtime: "dinner", //時間帯
        favorite: true, //お気に入り
        createdAt: serverTimestamp(), //更新時刻
    });
});
```

### ドキュメント群から一つずつ取り出して一部更新
```ex.js
updateDoc(doc.ref, { 連想配列 });
```

- 第一引数: リファレンス
- 第二引数: 上書き・新規追加する連想配列

指定したフィールドのみ更新します。
ドキュメントが保持していないフィールドが指定された場合、新しくフィールドを追加します。


```
//変更前
{
    name: "味噌汁",
    mealtime: "breakfast",
    date: {
        year: 2023, 
        month: 5, 
        day: 6
    },
    createdAt: serverTimestamp(),
}

//変更後
{ 
    name: "味噌汁", 
    mealtime: "dinner",
    date: {
        year: 2023, 
        month: 7, 
        day: 6
    },
    favorite: true,
    createdAt: serverTimestamp(),
}
```


# [余談] The query requires an index が出る

クエリの作成にて複数のフィールドを参照する場合、そのクエリは「複合クエリ」と見なされ、何も対処をしないとエラーが出ます。
<img width="493" alt="スクリーンショット 2023-05-10 16.01.43.png (115.6 kB)" src="https://img.esa.io/uploads/production/attachments/19973/2023/05/10/137427/65213ed1-ab76-4121-b56d-a4a45445ab17.png">

このようになった場合このエラーのリンクを踏み、下のような画面に遷移したら「保存」を押しインデックスの追加をします。
しばらくすると、このインデックスが有効になり問題なく動作するようになります。
<img width="564" alt="スクリーンショット 2023-05-10 16.02.03.png (399.2 kB)" src="https://img.esa.io/uploads/production/attachments/19973/2023/05/10/137427/9d8d66d1-5d03-4b5c-a293-3b73dab34eaa.png">

# おわりに
今回はFirestoreのversion9における基本的な関数を取り扱いました。
紹介した関数だけでもある程度Firestoreを扱うことができると考えます。
version9で苦労されている方のお役に立てれば幸いです。