本篇要解決的問題
在寫上一篇關於 監聽頁面關閉事件 的時候,為了要測試資料有沒有送出去,有用到 Firebase Realtime Database,因為它操作起來簡單,看資料、刪資料都很方便。
原本以為以前有寫過相關的筆記文,想找來直接複製貼上 code 使用,結果找了一下後才發現,咦?原來只有寫過怎麼在 Google Apps Script 上使用,卻沒寫過怎麼在 Client 端上使用!
另外有寫過怎麼在 Client 端上使用 Firebase Cloud Firestore,但就是沒有寫過 Realtime Database!
一陣驚為天人之下,就決定整理一下常用的 Realtime Database 取資料、寫資料功能,方便以後可以複製貼上查找。
本篇主要參考的資料是官方說明文件:Firebase Realtime Database Web。
建立 Realtime Database
在開始寫程式碼之前,我們要開啟 Firebase 上 Realtime Database 的功能。
進到 Firebase 後台,左側選單點選「Realtime Database」,再點擊「建立資料庫」:
即時資料庫的位置可以選一個距離比較近的,亞洲的話可以選新加坡。
選好儲存位置,下一步是選擇安全規則:

預設會是「鎖定模式」,就是全部的人都禁止讀取跟寫入資料,想改的話之後都可以改,我們直接按下「啟用」,之後會看見我們的資料庫建立完成,並且上方的頁籤是停在「資料」上:

修改安全規則
頁面點選上圖頁籤的「規則」,會進到修改安全規則的頁面:

完整安全規則的設定可以看官方文件:Basic Security Rules
這邊整理最常用到的 5 種情況:
Locked Mode 鎖定模式,全部阻擋
鎖定模式是預設的模式,所有人一律禁止讀寫資料:
{ "rules": { ".read": false, ".write": false } }
全部開放
跟鎖定模式相反,把 false
都改為 true
,就是開放給所有人使用:
{ "rules": { ".read": true, ".write": true } }
Test mode 測試模式,一段時間內開通讀寫功能
我們在建立資料庫時,除了鎖定模式下還有另一個測試模式可以選擇,就是限定一段時間內所有人都可以讀寫:
{ "rules": { ".read": "now < 1638892800000", // 2021-12-8 ".write": "now < 1638892800000", // 2021-12-8 } }
1638892800000
那一串數字是毫秒,取得方式很簡單,JS 中 console.log
以下就可以取得:
new Date('2021-12-8').getTime()
把 2021-12-8
換成我們想要的日期即可。
All authenticated users 有登入帳號即可使用
所謂的「有登入帳號」,是指有登入 Firebase 的帳號,Firebase 必須開通 Auth 功能,並且由此功能登入帳號。
怎麼使用 Firebase 的登入功能可看以下二篇:
Firebase Authentication 第三方登入 – Google、FB
Firebase Authentication 第三方登入 – GitHub
{ "rules": { ".read": "auth.uid != null", ".write": "auth.uid != null" } }
用 Firebase 的登入功能登入後,Firebase 就會派一組 uid
給使用者,因此 uid
就不會是 null
。
限定自己的帳號可以讀寫資料
這段是改寫上面的「有登入帳號即可使用」,把 null
改成我們的 uid
。
{ "rules": { ".read": "auth.uid == '我們的 uid'", ".write": "auth.uid == '我們的 uid'" } }
我們的 uid
在 Firebase 後台中可以看到,如圖:

所以我們就可以寫成:
{ "rules": { ".read": "auth.uid == 'IcygiRAG5KYfgGiwjqOaEQuT1122'", ".write": "auth.uid == 'IcygiRAG5KYfgGiwjqOaEQuT1122'" } }
安裝 Firebase SDK
Firebase SDK 在 Web 的版本上,寫這篇時是 V9,主推用模組整合工具,用 npm 或 yarn 安裝 Package 後,用 Webpack 等打包使用。
August 使用的工具是 CodeKit,可以直接使用 export、import,相當方便。
先在我們的檔案中安裝 Firebase 的 Package,資料夾中打開終端機,輸入:
npm install firebase // 或 yarn add firebase
安裝好 Firebase Package 後,接著要引用 SDK,引用的程式碼在 Firebase 主控台的「專案總覽 > 專案設定 > 網頁應用程式」頁面中的看到,再加上 Realtime Database 的部份,程式碼整理如下:
import { initializeApp } from 'firebase/app'; import { getDatabase } from "firebase/database"; const firebaseConfig = { apiKey: "{{apiKey}}", authDomain: "{{projectId}}.firebaseapp.com", databaseURL: "https://{{databaseName}}.firebaseio.com", storageBucket: "{{bucket}}.appspot.com" }; const app = initializeApp(firebaseConfig); const database = getDatabase(app);
其中 import { getDatabase } from "firebase/database"
這行,會因為我們之後要使用的功能不一樣,引用的功能也會做增加。
{{apiKey}}
、{{projectId}}
、{{bucket}}
這三個要替換成自己使用的 Firebase 專案設定值。
寫入 / 更新資料
在讀取資料前,先來塞個幾筆資料進 DB 吧~
寫入、更新資料看文件上分為這二種:
- 寫入:set
- 更新:update
但,不知道是不是哪裡寫錯,這二個用起來的結果是一樣的啊 = =
Realtime Database 的文件上看不到 Cloud Firestore 上有的 merge
參數,可以只更新有變動的部份。
文件上的 update
可以做到不覆蓋掉原本資料的方式,就是建一個亂數的序號當資料的 key,在這 key 裡塞資料。
嗯…但試了一下,在 set
時用一樣的方式,也是直接新增而不會覆蓋掉原本的資料啊 = =
可能是 August 誤會了什麼,如果有知道誤會地方的大大歡迎留言提供。
因為 August 實測 set
、update
出的結果都一樣,因此程式碼就寫成以下一段:
import { getDatabase, ref, set, update, push, child } from "firebase/database"; const db = getDatabase(); // set 寫入資料 function setData() { // 建立一組亂數序號 const newKey = push(child(ref(db), 'users')).key; // 寫入資料 set(ref(db, 'users/' + newKey), { username: 'set-username', }); } // update 更新資料 function updateData() { // 建立一組亂數序號 const newKey = push(child(ref(db), 'users')).key; // 更新資料 update(ref(db), { ['users/' + newKey]: { username: 'update-username' } }); }
執行 setData()
、updateData()
後,資料庫上會看到像這樣:

可以看到都是把資料塞進到新建的亂數 key 下,避免讓原有資料被整個覆蓋。
讀取資料
讀取數據分為二種狀況:自動監聽數據的變化、只讀取一次數據。
自動監聽數據的變化
頁面除了抓一次 Realtime Database 上的資料下來,當 DB 的資料有變動時,會自動再抓一次新資料。
import { getDatabase, ref, onValue } from "firebase/database"; const db = getDatabase(); const dbRef = ref(db, '/users/'); onValue(dbRef, snapshot => { console.log(snapshot.val()); });
Realtime Database 就是即時資料庫,當資料有變動時自動重抓一次資料,很適合做成像聊天室之類的功能。
只讀取一次數據
看文件,只讀取一次數據下來有二種方式。
優先從本地的 Cache 抓資料
import { getDatabase, ref, onValue } from "firebase/database"; const db = getDatabase(); const dbRef = ref(db, '/users/'); onValue(dbRef, (snapshot) => { console.log(snapshot.val()); }, { onlyOnce: true });
直接從 Server 上抓資料
import { getDatabase, ref, get, child } from "firebase/database"; const db = getDatabase(); const dbRef = ref(getDatabase()); get(child(dbRef, 'users/')).then((snapshot) => { if(snapshot.exists()) { console.log(snapshot.val()); } else { console.log('沒有資料'); } }).catch((error) => { console.error(error); });
刪除資料
刪除資料的方式有二種,一種是使用 remove()
,另一種是直接把值設為 null
。
我們假設在 DB 上的資料是這樣:
user: { a: { key: value }, b: { key2: value2 } }
我們要刪掉 user.a
的資料,二種刪除資料的方式如下:
import { getDatabase, ref, update, set, remove } from "firebase/database"; const db = getDatabase(); // remove remove(ref(db, 'users/a')); // 把資料改為 null:update update(ref(db), { ['users/a']: null }) // 把資料改為 null:set set(ref(db, 'users/a'), null);
關於離線處理
看了文件上說,即便使用者離線,Firebase 還是會運作,試了一下還真的可以,不過僅限於頁籤還沒關閉的狀態下。
看了一下 Network,發現 Realtime Database 是用 WebSocket 的,不知道是不是因為這樣,所以離線時還能處理數據?
因為看 維基 上面寫:
在WebSocket API中,瀏覽器和伺服器只需要完成一次交握,兩者之間就可以建立永續性的連接,並進行雙向資料傳輸。
同場加映,之前寫過的 WebSocket 筆記文:WebSocket 基本介紹及使用筆記。
總之,Realtime Database 有提供監測使用者離線時的事件。
以下是當使用者離線或斷線時,寫一筆資料到 DB 上的程式碼:
import { getDatabase, ref, set, onDisconnect } from "firebase/database"; const db = getDatabase(); const dfRef = ref(db, 'disconnectmessage'); onDisconnect(dbRef).set('離線囉~');

