EP03 | Firebase 中的資料庫 - 獲取 Cloud Firestore 資料庫

如何獲取數據

Firestore 文件提及:

有兩種方法可以檢索存儲在 Cloud Firestore 中的數據。這些方法中的任何一種都可以用於文檔、文檔集合或取得查詢結果

  • 調用一個方法來獲取數據 (get())。
  • 設置偵聽器以接收數據更改事件 (onSnapshot())。
    設置偵聽器時,Cloud Firestore 會向您的偵聽器發送數據的初始快照,然後在每次文檔更改時發送另一個快照。

必須注意到使用 get() 單純只是取得資料庫資料,並不具有獲取實時更新的效果,所以在添加資料或更新資料過後要再調用 get() 才可取得最新資料,而偵聽器 (onSnapshot()) 只需設置 1 次即可獲取實時更新。

這一篇文章主要紀錄 Firestore 中偵聽器 (onSnapshot()) 的學習。

使用偵聽器

偵聽文檔

1
2
3
4
5
6
7
8
9
const todoCollection = db.doc('/todos/JUN/todo/HnJNwCjH2Wv7qELj9nRO');
todoCollection.onSnapshot(snapshot => {
// 查看 firestore 文檔預設屬性及方法
console.log(snapshot)
// 透過 data() 取得文檔內容
console.log(snapshot.data())
}, error => {
console.log('err:', err)
})

偵聽集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const todoCollection = db.collection('todos').doc('JUN').collection('todo');

todoCollection.onSnapshot(snapshot => {
// 查看 firestore 文檔預設屬性及方法
console.log(snapshot)
// 透過 docs 取得集合內文檔陣列,如果想要針對每個文檔做動作,可以使用 forEach()
console.log(snapshot.docs)
snapshot.docs.forEach(doc => {
// doc代表每個文檔
// 要取得文檔內容,同樣要用 data() 來取得
console.log(doc.data())
})
}, error => {
console.log('err:', err)
})

Snapshot 屬性及方法:

  • exists : 文檔是否存在,存在即為 true

  • id : 文檔 ID 。

  • metadata : 資料的狀態。

    • fromCache : 資料來源, true 代表來自 Cache ,false 代表來自 Server 。

    • hasPendingWrites : 是否還有尚未寫入到後端的本地資料, true 代表有資料在本地端尚未寫入後端 ,false 代表來自資料已經被寫入後端。(偵聽器將在數據發送到後端之前收到新數據通知。)

  • data() : 取得數據。(偵聽文檔)

  • docs : 取得文檔陣列。(偵聽集合)

有關於 SnapshotListenOptions (includeMetadataChanges) ,設定為 true ,可以幫助我們除了資料的更改時會收到 snapshot 外,在 metadata 有變化的時候也會收到 snapshot 。

偵聽查詢文檔

資料使用 Firestore 提供範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const citiesRef = db.collection("cities");

citiesRef.doc("SF").set({
name: "San Francisco", state: "CA", country: "USA",
capital: false, population: 860000,
regions: ["west_coast", "norcal"] });
citiesRef.doc("LA").set({
name: "Los Angeles", state: "CA", country: "USA",
capital: false, population: 3900000,
regions: ["west_coast", "socal"] });
citiesRef.doc("DC").set({
name: "Washington, D.C.", state: null, country: "USA",
capital: true, population: 680000,
regions: ["east_coast"] });
citiesRef.doc("TOK").set({
name: "Tokyo", state: null, country: "Japan",
capital: true, population: 9000000,
regions: ["kanto", "honshu"] });
citiesRef.doc("BJ").set({
name: "Beijing", state: null, country: "China",
capital: true, population: 21500000,
regions: ["jingjinji", "hebei"] });
  1. 建立查詢對象:

    利用 db.collection(集合名).where(給定屬性, 比較運算符, 給定值) 來建立查詢對象。

    Firestore 的比較運算符有下列幾種:

    • < : 小於。

    • <= : 小於或等於。

    • == : 等於。

    • > : 大於。

    • >= : 大於或等於。

    • != : 不等於。

    • in : 返回與給定值(可為多個)其中之一匹配的文檔,使用 == 及邏輯 OR 。

  2. 取得或偵聽查詢結果

    透過 get()onSnapshot() 來取得或偵聽查詢結果。

    1
    2
    3
    4
    5
    6
    //country 是字串
    db.collection('cities').where('country', 'in', ['USA', 'Japan']).get().then((query)=>
    {
    console.log(query.docs)
    // DC、LA、SF、TOK
    })
    • not-in : 返回與給定值(可為多個)皆不匹配(且不為null)的文檔,使用 != 及 邏輯 AND 。

      1
      2
      3
      4
      5
      6
      //country 是字串
      db.collection('cities').where('country', 'not-in', ['USA', 'Japan']).get().then((query)=>
      {
      console.log(query.docs)
      // BJ
      })

    (對陣列)

    • array-contains : 陣列根據給定值進行過濾。

      1
      2
      3
      4
      5
      6
      7
      // regions 是陣列
      db.collection('cities').where('regions', 'array-contains',
      'west_coast').get().then((query)=>
      {
      console.log(query.docs)
      // SF、LA
      })
    • array-contains-any : 陣列根據給定值(可為多個)進行過濾,返回符合給定值其中一個所有文檔。

      1
      2
      3
      4
      5
      6
      7
      8
      // regions 是陣列
      db.collection('cities').where('regions', 'array-contains-any',
      ['west_coast', 'east_coast']).get().then((query)=>
      {
      console.log(query.docs)
      // SF、LA、DC
      })

指定數據的排序及限制數量

使用 orderBy() 指定數據的排序順序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 以城市的人口數作為排序少→多(升冪)
db.collection('cities').orderby('population')
.onSnapshot(
snapshot=>{console.log(snapshot)},
err=>{console.log(err)}
)

// 以城市的人口數作為排序多→少(降冪)
db.collection('cities').orderby('population', 'desc')
.onSnapshot(
snapshot=>{console.log(snapshot)},
err=>{console.log(err)}
)

// 多個排序
//先以人口做排序,相同人口的以城市名作為排序
db.collection('cities').orderby('population').orderby('name')
.onSnapshot(
snapshot=>{console.log(snapshot)},
err=>{console.log(err)}
)

使用 limit() 限制檢索的文檔數量。

1
2
3
4
5
// 結合 limit 限制得到的資料筆數
db.collection('cities').orderby('population').limit(3)
.onSnapshot(
snapshot=>{console.log(snapshot)},
err=>{console.log(err)}

使用查詢游標對數據進行分頁

查詢游標定義查詢的起點和終點,允許您:

  • 返回數據的子集。
  • 分頁查詢結果。

然而,定義一個特定範圍的查詢時,你應該使用 where() 中描述的方法簡單查詢。

簡單說明一下,查詢的起點跟終點分別使用以下幾種方法:

  • startAt(‘指定起點’) :包括指定起點。

  • startAfter(‘指定起點’) :不包括指定起點。

  • endAt(‘指定終點’) :包括指定終點。

  • endBefore(‘指定終點’) :不包括指定終點。

1
2
3
4
5
6
7
8
// 必須先定義一個查詢範圍,並且以人口數排列
const citiesRef = db.collection('cities').orderBy('population')

// 以人口數排列後再指定查詢起點
// 返回人口數1000以上的文檔資料
citiesRef.startAt(1000).get().then((queryshot)=>{
console.log(queryshot)
})

使用文檔快照定義查詢游標

將文檔快照中的值用作查詢游標中的起點或終點。

1
2
3
4
5
6
7
8
9
10
11
db.collection('cities').doc('TOK').get().then((doc)=>{
db.collection('cities')
// 以人口數做排序
.orderBy('population')
// 以 TOK 人口數做起點基準,得到 >= TOK 人口數的檔案
.startAt(doc)
.get().then(queryshot => {
console.log(queryshot.docs)
// TOK、BJ
})
})

分頁查詢

查詢游標結合 limit() 做分頁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let allDataLen = 0
let perPage = 2
let pages = 0
let page_now = 2
let nextAfter = ''
// 目標為找出 page_now 的資料
// 先以人口數做排序
db.collection('cities').get().then(queryshot=>{
allDataLen = queryshot.length
pages = allDataLen / perPage

// 用 limit() 找出前一頁最後一筆資料
db.collection('cities').orderBy('population').limit((page_now-1)*perPage).get().then(last=>{
// 找出前一頁最後一筆資料
let nextAfter = last.docs[last.docs.length-1]
console.log(last.docs[last.docs.length-1]) // SF

// 找出目前頁面的資料
db.collection('cities').orderBy('population').startAfter(nextAfter).limit(perPage).get().then(next=>{
// 目前頁面的資料
console.log(next.docs) // LA、TOK
})
})
})

參考資料

Get data with Cloud Firestore

Get realtime updates with Cloud Firestore

Perform simple and compound queries in Cloud Firestore

使用 Cloud Firestore 訂購和限制數據

使用查詢游標對數據進行分頁