【React】Firestoreの使い方

## そもそもFirestoreとは
- FirestoreはGoogle Cloudが提供するNoSQLドキュメントデータベース
- Firebaseの一つの機能

## Firebaseとは
- Googleが提供している`mBaaS`(mobile Backend as a Service)
- DBやバックエンド、デプロイなどの機能を提供してくれるサービス
- バックエンドを作らず、Webアプリやモバイルアプリを作ることができる

## 用語
|用語|意味|
|:-:|:-:|
|コレクション|データテーブル的なもの|
|ドキュメント|レコード的なもの|
|フィールド|カラム的なもの|
|サブコレクション|データテーブルの中にネストしたデータテーブル|

## 特徴
### Firestoreが提供しているクエリにはテーブル結合機能が無い
コレクション（データテーブル）をテーブル結合するための機能はFirestoreは提供していない。代わりにコレクションの中にコレクションを追加できる`サブコレクション`機能がある。

### デフォルトのデータベース
Firestoreを利用すると自動的にデフォルトのデータベースとして`defalut`が用意される

## Reactで使うための準備

### 1.firebaseの初期化
```ts:src/db/firebase.ts
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  apiKey: "xxxxxxxx",
  authDomain: "my-app.firebaseapp.com",
  projectId: "my-app",
  storageBucket: "my-app.firebasestorage.app",
  messagingSenderId: "xxxxxx",
  appId: "xxxxxx",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);

export { db, auth };

```

## データの取得
データの取得は`document`参照インスタンスからしか取得できない

### ドキュメントIDを指定してドキュメントを取得
```ts
import { db } from "@/db/firebase";
import { doc, getDoc } from "firebase/firestore";

const collectionName = "users";
const docId = "user-1";

// ドキュメント参照インスタンスを指定
const docRef = doc(db, collectionName, docId);

// ドキュメント情報を取得
const docSnap = await getDoc(docRef);

if (docSnap.exists()) {
  console.log("Document data:", docSnap.data());
} else {
  console.log("No such document!");
}
```

### サブコレクションのドキュメントを取得
```ts
import { db } from "@/db/firebase";
import { doc, getDoc } from "firebase/firestore";

// userドキュメントの中のtodos(サブ)コレクションからtodoドキュメントを取得

const userId="user-1";

// userドキュメントの参照
const userRef = doc(db, "users", userId);

// サブコレクションの参照
const todosRef = collection(userRef, "todos");

const todoId="todo-1";

// todoドキュメントの参照
const todoRef = doc(todosRef, todoId);

// ドキュメント情報を取得
const docSnap = await getDoc(todoRef);

if (docSnap.exists()) {
  console.log("Document data:", docSnap.data());
} else {
  console.log("No such document!");
}
```
サブコレクションのドキュメントを指定する際に、ファイルパスのように指定することで短くできる。

```ts
const userId="user-1";
const todoId="todo-1";

const todoRef = doc(db, `users/${userId}/todos`, todoId);

/* 上記は下記と同じこと*/
// const userRef = doc(db, "users", userId);
// const todosRef = collection(userRef, "todos");
// const todoRef = doc(todosRef, todoId);
```

### フィールド情報で条件検索しドキュメントを取得
```ts
import { db } from "@/db/firebase";
import { doc, getDocs, query, where } from "firebase/firestore";

const userId = "user-1";

// ドキュメント参照インスタンスを指定
const usersRef = doc(db, "users", userId);

// クエリを作成
const q = query(usersRef, where("name", "==", ken));

// クエリ実行
const docsSnapshot = await getDocs(q);

if (docsSnapshot.empty) {
  console.log("No such document!");
} else {
  docsSnapshot.docs.forEach((doc) => {
    console.log(doc.id, " => ", doc.data());
  })
}
```

## データの追加
:::note info
##### Firestoreはコレクションを自動作成する
Firestoreには「コレクションを追加する」直接的な関数は無く、コレクションは自動的に作成される仕組みになっている。具体的には、コレクションの中にドキュメントを追加することで、そのコレクションが自動的に生成される。
:::

### ドキュメントの追加（ID指定）
ドキュメントIDを指定して追加する場合は`setDoc`関数を使う
```ts:ドキュメントの追加（ID指定）
import { db, setDoc, FirestoreError } from "@/db/firebase";

interface UserState {
  userId: string;
  userName: string;
  mail: string;
}

export const createUser = async ({ userId, ...userData }: UserState): Promise<void> => {

  const collectionName = "users";
  
  // コレクション参照インスタンスの取得
  const userCollectionRef = collection(db, collectionName);

  // 追加ドキュメントインスタンス取得
  const addDocRef = doc(userCollectionRef, userId);

  try {
    // ドキュメント追加
    await setDoc(addDocRef, userData);
  } catch (error) {
    if (error instanceof FirestoreError) {
      console.error("Error adding document: ", error.message);
    }
  }
};
```
## クエリ演算子
[クエリ演算子に関する公式ページ](https://firebase.google.com/docs/firestore/query-data/queries?hl=ja&_gl=1*9x4286*_up*MQ..*_ga*MTA4MDQ0NTg2NC4xNzM1OTcyNzk2*_ga_CW55HF8NVT*MTczNTk3Mjc5NS4xLjAuMTczNTk3Mjc5NS4wLjAuMA..#query_operators "クエリ演算子に関する公式ページ")


## クエリの条件指定

### AND条件
```ts
export const queryAnd = async () => {
  const userId = "user-1";
  const usersRef = collection(db, `users/${userId}`);
  const q = query(usersRef, where(name, "!=", "KEN"), where("mail", "==", "test@sample.com"));
  const docsSnap = await getDocs(q);
};
```

### OR条件
```ts
export const queryOr = async () => {
  const userId = "user-1";
  const usersRef = collection(db, `users/${userId}`);
  const q = query(usersRef, 
              or(where(name, "!=", "KEN"), where("mail", "==", "test@sample.com")));
  const docsSnap = await getDocs(q);
};
```

### 指定したdocumentID以外のドキュメントを取得
```ts
import {collection, query, where,getDocs documentId } from "firebase/firestore";
export const queryOtherDocumentId = async () => {
  const userId = "user-1";
  const usersRef = collection(db, `users`);

  // documentIDを検索条件に使う場合はdocumentId()を使う
  const q = query(usersRef, where(documentId(), "!=", userId));
  const docsSnap = await getDocs(q);
};
```