Firebase Cloud Firestore V9 常用功能筆記
Tags: firestore, v9, google, firebase
Published: 2022-06-20 08:23:48

## 本篇大綱

- [本篇要解決的問題](https://www.letswrite.tw/cloud-firestore-init-v9/#%e6%9c%ac%e7%af%87%e8%a6%81%e8%a7%a3%e6%b1%ba%e7%9a%84%e5%95%8f%e9%a1%8c)
- [安裝 Firebase SDK](https://www.letswrite.tw/cloud-firestore-init-v9/#%e5%ae%89%e8%a3%9d-firebase-sdk)
 - [測試模式](https://www.letswrite.tw/cloud-firestore-init-v9/#%e6%b8%ac%e8%a9%a6%e6%a8%a1%e5%bc%8f)
 - [有登入即可使用使用](https://www.letswrite.tw/cloud-firestore-init-v9/#%e6%9c%89%e7%99%bb%e5%85%a5%e5%8d%b3%e5%8f%af%e4%bd%bf%e7%94%a8%e4%bd%bf%e7%94%a8)
 - [一律阻擋](https://www.letswrite.tw/cloud-firestore-init-v9/#%e4%b8%80%e5%be%8b%e9%98%bb%e6%93%8b)
 - [一律通過](https://www.letswrite.tw/cloud-firestore-init-v9/#%e4%b8%80%e5%be%8b%e9%80%9a%e9%81%8e)
- [寫入資料](https://www.letswrite.tw/cloud-firestore-init-v9/#%e5%af%ab%e5%85%a5%e8%b3%87%e6%96%99)
 - [set 建立、覆蓋資料](https://www.letswrite.tw/cloud-firestore-init-v9/#set-%e5%bb%ba%e7%ab%8b%e3%80%81%e8%a6%86%e8%93%8b%e8%b3%87%e6%96%99)
 - [update 更新資料](https://www.letswrite.tw/cloud-firestore-init-v9/#update-%e6%9b%b4%e6%96%b0%e8%b3%87%e6%96%99)
- [取得資料](https://www.letswrite.tw/cloud-firestore-init-v9/#%e5%8f%96%e5%be%97%e8%b3%87%e6%96%99)
 - [取一次資料](https://www.letswrite.tw/cloud-firestore-init-v9/#%e5%8f%96%e4%b8%80%e6%ac%a1%e8%b3%87%e6%96%99)
 - [監聽即時資料更新](https://www.letswrite.tw/cloud-firestore-init-v9/#%e7%9b%a3%e8%81%bd%e5%8d%b3%e6%99%82%e8%b3%87%e6%96%99%e6%9b%b4%e6%96%b0)
 - [停止資料監聽](https://www.letswrite.tw/cloud-firestore-init-v9/#%e5%81%9c%e6%ad%a2%e8%b3%87%e6%96%99%e7%9b%a3%e8%81%bd)
- [查詢資料](https://www.letswrite.tw/cloud-firestore-init-v9/#%e6%9f%a5%e8%a9%a2%e8%b3%87%e6%96%99)
- [刪除資料](https://www.letswrite.tw/cloud-firestore-init-v9/#%e5%88%aa%e9%99%a4%e8%b3%87%e6%96%99)

---

## 本篇要解決的問題

之前寫過一篇 [Version 8](https://www.letswrite.tw/cloud-firestore-init/) 版的，結果人生就是第 8 版都還沒用熟，就有了第 9 版，就像 Tailwind CSS 一樣啊，V2 版才記得剛補上段落，就這樣又出了 V3 是怎麼回事，這怎麼弄麻。

因為 Version 8 那篇很長，找不到地方塞第 9 版的說明進去，就專門寫成這篇筆記文囉。

主要參考文件是 Firebase 上的官方文件：[Cloud Firestore](https://firebase.google.com/docs/firestore)

---

## 安裝 Firebase SDK

先進入到 Firebase 的後台建立一個專案。

接著 Firebase 後台左側選單，選擇「Firestore Database」，右側點擊「建立資料庫」：

![建立資料庫](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/doiddc5tyz7bkif13iqq.jpg)

下一步，會出現安全規則的選項，有正式模式、測試模式，這之後可以修改，參照 [說明文件](https://firebase.google.com/docs/firestore/security/get-started)，總共有四種安全模式可以用，說明如下。

### 測試模式

一段時間之內，路人也都可以讀寫資料庫。

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time < timestamp.date(2022, 2, 16);
    }
  }
}
```

### 有登入即可使用使用

這邊的「有登入」，指的是有登入同一個 Firebase 專案下的帳號，Authentication 的功能必須打開，並且登入，之前有寫過三篇，可以點開文章看怎麼使用登入功能：

[帳密登入](https://www.letswrite.tw/firebase-auth-email/)、[Google || FB 登入](https://www.letswrite.tw/firebase-auth-google-fb/)、[GitHub 登入](https://www.letswrite.tw/firebase-auth-github/)

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}
```

### 一律阻擋

不管誰來都阻擋，都無法讀寫資料庫：

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}
```

### 一律通過

不管是誰都可以讀寫資料庫，外星人來也可以：

```javascript
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}
```

---

安全規則選定後，接著會選資料庫要放在哪個地區，這部份可以直接看文件，裡面有各個地區的資料庫主機是在哪個國家：

[Regional locations](https://firebase.google.com/docs/firestore/locations)

---

建立好 Cloud Firestore 後，Firebase 左側選單選擇專案總覽，右側點擊新增網頁應用程式：

![新增應用程式](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brxak9qh1arwdto2siiq.jpg)

在建立完應用程式後，就會看到要怎麼安裝 Firebase SDK：


![如何安裝 Firebase SDK](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zmtxol3x1cf95fl5jse8.jpg)

參照說明文件，我們只需要以下幾行就可以：

```javascript
import { initializeApp } from "firebase/app"
import { getFirestore } from "firebase/firestore"
const firebaseApp = initializeApp({
  apiKey: 'XXXXXXXXXXX',
  authDomain: 'XXXXXXXXXXX',
  projectId: 'XXXXXXXXXXX'
});

const db = getFirestore();
```

要記得，我們的檔案裡也要安裝 Firebase 的 package：

```shell
npm install firebase
```

---

## 寫入資料

Firebase Cloud Firestore 的資料架構有三層，Collections > Documents > Data。

說明如下圖：

![Firebase Cloud Firestore 資料架構](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fh8sq4ed6h05jfvspy3l.jpg)

### set 建立、覆蓋資料

第一種寫入資料的方式是 `setDoc`，可以指定 `document` 的名稱：

```javascript
import { getFirestore, doc, setDoc } from "firebase/firestore"
const db = getFirestore();
await setDoc(doc(db, "collection 的名稱", "document 的名稱"), 資料, { merge: true });
```

`merge` 是指要不要只覆蓋有更動過的資料的部份，`true` 可以避免原有的資料整個被覆蓋或被刪除掉，作用跟 `update` 相同。

範例：

```javascript
try {
  await setDoc(doc(db, "users", "demo1"), {
    web: "Let's Write",
    author: "August",
    like: true
  });
} catch (err) {
  console.error("Error: ", err);
}
```

第二種寫入資料的方式是 `addDoc`，適用於如果不想要指定 `document` 的名稱，想每次都直接配一個亂數時使用：

```javascript
import { getFirestore, collection, addDoc } from "firebase/firestore"
const db = getFirestore();
let docRef = await addDoc(collection(db, "collection 的名稱"), 資料);
console.log(docRef.id); // => 亂數值
```

範例：

```javascript
try {
  let docRef = await addDoc(collection(db, "users"), {
    web: "Let's Write",
    author: "August"
  });
  console.log(docRef.id)
} catch (err) {
  console.error("Error: ", err);
}
```

這樣不管送了幾次重覆的資料，由於 `document` 的名稱是亂數，就不會被誤蓋掉，資料庫上會看到像這樣：

![隨機建立亂數 document id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cssunu5b6i40ttabhwfi.jpg)

### update 更新資料

`update` 就是只會更新資料有變動的部份，不會原有的資料整個蓋掉，作用跟 `setDoc` 的 `merge` 設為 `true` 一樣。

```javascript
import { getFirestore, updateDoc } from "firebase/firestore"
const db = getFirestore();
await updateDoc(doc(db, "collection 的名稱", "document 的名稱"), 要更新的資料);
```

範例：

```javascript
try {
  await updateDoc(doc(db, "users", "demo1"), {
    web: "Let's Write",
    author: "Augusts"
  });
} catch (err) {
  console.error("Error: ", err);
}
```

---

## 取得資料

取得資料分成二種：只取一次、監聽即時資料更新。

### 取一次資料

#### getDoc 取單一 document 的資料

如果是要取得某個 `collection` 裡，單一 `document` 的資料：

```javascript
import { getFirestore, doc, getDoc } from "firebase/firestore";
const db = getFirestore();
const docSnap = await getDoc(doc(db, "collection 的名稱", "document 的名稱"));
if(docSnap.exists()) {
  console.log(docSnap.data())
}
```

#### getDocs 取所有 document 的資料

如果是要取得某個 `collection` 裡，所有 `document` 的資料：

```javascript
import { getFirestore, collection, getDocs } from "firebase/firestore";
const db = getFirestore();
const querySnapshot = await getDocs(collection(db, "collection 的名稱"));
querySnapshot.forEach(doc => {
  console.log(doc.id, doc.data());
});
```

### 監聽即時資料更新

#### 取得單一 document 資料變動

監聽某一個 `document` 裡的資料變動，當 `document` 裡的值一有變動，就會回傳 `document` 裡所有的值。

```javascript
import { getFirestore, doc, onSnapshot } from "firebase/firestore";
const db = getFirestore();
const unsub = onSnapshot(doc(db, "collection 的名稱", "document 的名稱"), (doc) => {
  console.log(doc.data());
});
```

#### 取得取有 document 的資料變動

監聽某個 `collection` 裡所有 `document` 裡資料的任何變動，當其中一個 `document` 裡的某個值一有變動，就會回傳 `collection` 裡所有 `document` 裡的值。

```javascript
const unsubscribe = onSnapshot(collection(db, 'users'), (querySnapshot) => {
  querySnapshot.forEach((doc) => {
    console.log(doc.data())
  });
});
```

### 停止資料監聽

Firebase Cloud Firestore 是用讀寫次數在收費的（一個月有免費額度），如果每個使用者一進到頁面上，所有欄位都監聽變動，就很有可能會要收費，因此 Firestore 有提供停止監聽資料變動的方法。

其實從上面監聽資料變動的命名變數上，就可以猜到使用方式，這也是 August 看文件時驚訝的。

#### 停止 取得單一 document 資料變動

```javascript
import { getFirestore, doc, onSnapshot } from "firebase/firestore";
const db = getFirestore();
const unsub = onSnapshot(doc(db, "collection 的名稱", "document 的名稱"), (doc) => {
  console.log(doc.data());
});

unsub(); // 停止監聽
```

#### 停止 取得取有 document 的資料變動

```javascript
const unsubscribe = onSnapshot(collection(db, 'users'), (querySnapshot) => {
  querySnapshot.forEach((doc) => {
    console.log(doc.data())
  });
});

unsubscribe(); // 停止監聽
```

真的方便~ 直接執行 function 就可以了。

---

## 查詢資料

我們在取資料庫中的資料時，可以不用整個 `collection` 或整個 `document` 都取回來後，再自己去把要的資料濾出來，Firestore 有提供查詢的功能，可以只返還我們要查的資料。

```javascript
const ref = collection(db, "users");
const q = query(ref, where("web", "==", "Let's Write"));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
  console.log(doc.id, doc.data());
});
```

查詢用的運算符有很多……多到 August 不想研究了（懶），就附上文件說明啦~

[Query operators](https://firebase.google.com/docs/firestore/query-data/queries#query_operators)

---

## 刪除資料

### 刪除 data

要刪除 `document` 裡的某個資料：

```javascript
import { getFirestore, doc, updateDoc, deleteField } from "firebase/firestore";
const db = getFirestore();
await updateDoc(doc(db, 'collection 的名稱', 'document 的名稱'), {
  要刪除的欄位名稱: deleteField()
});
```

範例：

```javascript
await updateDoc(doc(db, 'users', 'MTjUUsKj4hqKNZ6HqTmp'), {
  author: deleteField()
});
```

### 刪除 document

要刪除 `collection` 裡的某個 `document`：

```javascript
import { getFirestore, doc, deleteDoc } from "firebase/firestore";
const db = getFirestore();
await deleteDoc(doc(db, "collection 的名稱", "document 的名稱"));
```

範例：

```javascript
await deleteDoc(doc(db, "users", "MTjUUsKj4hqKNZ6HqTmp"));
```

---

## 刪除 collection

文件中說明不提供這樣子的 API，因為很危險，哪個人發神經一執行，就整個 `collection` 會從地球上消失。

如果真的要刪 `collection`，就是執行上面那段的「刪除 document」，然後一個一個 `document` 去刪。