Cloud Firestore のメソッドを使っていて、返ってきた値の扱い方について時々混乱することがありました。
Snapshot の種類と違いをもっと詳細に把握しておけば解決することにふと気づき、調べてみました。
類似記事
書き終わってから似た記事があることに気づきました。
まとめ方や重点の置き方に違いがあるので、それぞれに良さがあるのではないかと思います。
コード例などの言語
TypeScript と Dart/Flutter を使いながら調査しました。
記事中のコードは TypeScript にしていて、説明には Dart のことも含めています。
ウェブ SDK の言語は JavaScript と表現したりもしています。
他の言語で扱うときも大きくは違わないと思います。
クライアント用の SDK で確認したため、Admin SDK では異なるかもしれません。
関連する様々な型
Snapshot を細かく見る前に、よく使うプロパティの型やメソッド/ゲッターが返す値の型を全般的に見ておきましょう。
Referenceとget()
db.collection(...) // CollectionReference
.get() // Promise<QuerySnapshot>
db.collection(...) // CollectionReference
.doc(...) // DocumentReference
.get() // Promise<DocumentSnapshot>
CollectionReference と DocumentReference は文字通り collection と document を指す参照なのでわかりやすいと思います。
そのどちらにも get() があり、基にするのが collection か document かで戻り値が異なります。
それは当然ですが、頭の中であまり区別せずに使ってしまうと混乱の原因になりそうです。
クエリ条件指定
db.collection(...) // CollectionReference
.where(...) // Query
.order(...) // Query
.limit(...) // Query
db.collectionGroup(...) // Query
先ほど collection() の戻り値は CollectionReference でしたね。
一方、collectionGroup() は Query を返します。
Query はクエリ条件を指定するメソッド(where() など)が返すものです。
collectionGroup() も複数の同名の collection をまとめて取得する条件を指定するものであるせいか、where() 等に近い扱いになっているようです。1
複数のdocumentを取得
const snapshot = await db.collection(...).get() // QuerySnapshot
snapshot
.docs // QueryDocumentSnapshot[]
.forEach(doc => { ... }) // QueryDocumentSnapshot
// JavaScript SDKでは直接forEach()できる
snapshot
.forEach(doc => { ... }) // QueryDocumentSnapshot
CollectionReference を使って get() すると QuerySnapshot が得られます。
その中の docs は 配列になっています。
配列の中身の QueryDocumentSnapshot は後で詳しく見ます。
なお、JavaScript 用の SDK では QuerySnapshot のクラスに forEach() メソッドがあるので、docs の配列を取り出すことなく直接 forEach() で中身を順に取り出せます。
Dart/Flutter の SDK では直接はできません。
親を遡る
const doc = await db.doc('xxx/xxx').get() // DocumentSnapshot
const ref1 = doc.ref // DocumentReference
const ref2 = ref1.parent // CollectionReference
const ref3 = ref2.parent // DocumentReference(ないのでnull)
const d = await ref3?.get() // DocumentSnapshot(ref3がnullなのでgetされずundefined)
QueryDocumentSnapshot や DocumentSnapshot の ref で DocumentReference を得ることができます。
Dart では reference です。
parent を使って CollectionReference、更にその上の DocumentReference と遡っていけます。
ルート collection の参照で parent を使うと null が返ることに注意しましょう。
get() の結果は先ほどと違って QueryDocumentSnapshot ではなく DocumentSnapshot になります。
リアルタイムな監視
db.collection(...)
.onSnapshot(snapshot => { ... }) // QuerySnapshot
db.collection(...)
.doc(...)
.onSnapshot(snapshot => { ... }) // DocumentSnapshot
onSnapshot(Dart では snapshots)でリアルタイムに更新を監視すると、コールバック関数に QuerySnapshot や DocumentSnapshot が渡されます。
三種類のSnapshot
「Snapshot」が付くものが三つ出てきました。
私が特に混乱していたのはその三つです。
関連する様々な型 で既に概ね掴めたかもしれませんが、詳細を見ていきます。
※以下、プロパティが Dart ではゲッターになっている場合がありますが、特記は一部を除いて省略します。
QuerySnapshot
CollectionReference で get() すると得られるものです。
クエリの結果として得られた QueryDocumentSnapshot のオブジェクトが docs という配列に入ります。
db.collection(...) // CollectionReference
.get() // Promise<QuerySnapshot>
db.collection(...) // CollectionReference
.onSnapshot(snapshot => { ... }) // QuerySnapshot
DocumentSnapshot / QueryDocumentSnapshot とは異なり、get() や data() はありません。
それら二つとは異質なので区別しやすいと思います。
主なプロパティ/メソッド
-
docs
- クエリの結果として得られた document(
QueryDocumentSnapshot)のオブジェクトの配列- document がなければ空の配列になります。
- ここから document の情報を取り出して表示等に使うのが主な使い方になるかと思います。
- クエリの結果として得られた document(
-
size
- クエリの結果として得られた document の数
-
metadata
-
SnapshotMetadataという型のデータ- バックエンドに保存できていないデータがあるか、キャッシュから取り出したデータか、といった情報を持っています。
-
-
docChanges
- 前回から変化があった document の配列
-
onSnapshot(snapshot => { ... })で変更を監視してリアルタイム更新するのに使えます。 - 最初に取得したときには前回のデータがないので全ての document の情報が入っています。
-
- Javascript ではメソッド、Dart ではゲッター
- 前回から変化があった document の配列
DocumentSnapshot
DocumentReference で get() すると得られるものです。
document のデータを持っていて、data() で Map として取り出すことができます。2
データがない場合もあります。
const doc = await db
.doc('xxx/xxx/xxx/xxx') // DocumentReference
.get() // DocumentSnapshot
const ref = doc.ref.parent.parent // DocumentReference
await ref?.get() // DocumentSnapshot
db.collection(...) // CollectionReference
.doc(...) // DocumentReference
.onSnapshot(snapshot => { ... }) // DocumentSnapshot
主なプロパティ/メソッド
-
id
- document の ID です。
-
metadata
-
QuerySnapshotのmetadataと同じ
-
-
exists
- document の有無を表す bool 値
-
ref
- document の参照
- Dart では
reference
-
data()
- document のデータを返す
- データがなければ JavaScript では
undefined、Dart ではnullが返ります。
- データがなければ JavaScript では
- document のデータを返す
-
get()
- document のフィールドの値を返す
- フィールドの名前かパスを引数に取ります。
- document のフィールドの値を返す
QueryDocumentSnapshot
CollectionReference で get() して得た QuerySnapshot の docs 内にあるものです。
const snapshot = await db.collection(...).get() // QuerySnapshot
snapshot
.docs // QueryDocumentSnapshot[]
.forEach(doc => { ... }) // QueryDocumentSnapshot
DocumentSnapshot を継承していて API の見た目は同じです。
ただし、exists に違いが見られます。
「Query」という言葉が付いていることからイメージできるように、クエリの結果として得られたものであり、QuerySnapshot の docs に入っている時点で確実に存在するので、exists は常に true になります。
参考
API リファレンス
- ウェブ
- Flutter