Edited at

firestoreを使うときに理解しておきたいいろんなクラスを整理する


概要

firestoreからデータを取得するときに、CollectionReferenceとかQuerySnapshotとかいろんなクラスやインターフェースが出てきすぎて悲しみに暮れたのでまとめました。記事中ではjavascriptクライアントを例に取っていますが、他の言語でもそんなに話は変わらないのではないかと思います。


登場するクラスやインターフェース

普通にfirestoreを使ってるとよく使うことになるであろう以下のクラスやインターフェースについて役割や使い方を確認していきます。その他のクラスやインターフェースについては リファレンス を見ると良いでしょう。


  • CollectionReference

  • Query

  • DocumentReference

  • QuerySnapshot

  • QueryDocumentSnapshot

  • DocumentSnapshot


説明に使用する例

公式ドキュメント に出てくる都市のデータベースを例として使います。この記事では、以下のクエリで作成されるcitiesというコレクションがあるものとして進めます。

var citiesRef = db.collection("cities");

citiesRef.doc("SF").set({
name: "San Francisco", state: "CA", country: "USA",
capital: false, population: 860000 });
citiesRef.doc("LA").set({
name: "Los Angeles", state: "CA", country: "USA",
capital: false, population: 3900000 });
citiesRef.doc("DC").set({
name: "Washington, D.C.", state: null, country: "USA",
capital: true, population: 680000 });
citiesRef.doc("TOK").set({
name: "Tokyo", state: null, country: "Japan",
capital: true, population: 9000000 });
citiesRef.doc("BJ").set({
name: "Beijing", state: null, country: "China",
capital: true, population: 21500000 });


firestoreからのデータ取得の流れ

具体的な話の前に、firestoreからデータを取得するときに、クライアント側でどういう処理をするかの全体像を確認しておきます。


  1. データへの参照を作成する(例えば、db.collection('cities')db.collection('cities').doc('TOK')

  2. 参照からスナップショットを得る(get()メソッド)

  3. スナップショットからデータを得る(data()メソッド)

ここで、1の参照の作成と、3のスナップショットからデータへの変換は同期的で、2の参照からスナップショットへの変換だけが非同期(Promiseが返ってくる)ことを覚えておくと良いでしょう。

それでは、"参照"と"スナップショット"について、具体的に以下で見ていきます。


参照(CollectionReferenceQueryDocumentReference

CollectionReferenceQueryDocumentReferenceはいずれもデータへの参照です。このうち、CollectionReferenceQueryはほとんど同じもので、いずれも複数のドキュメントへの参照です。DocumentReferenceは一つのドキュメントへの参照です。


CollectionReference

CollectionReferenceはコレクションやサブコレクションへの参照で、以下のように作成できます。

const citiesRef = db.collection('cities')

CollectionReferenceを使って、コレクションにドキュメントを追加することができます。

citiesRef.add(data)

また、条件を絞り込んだり、ドキュメントのidを指定することで、後述するQueryDocumentReferenceを得ることができます。

const capitalsRef = citiesRef.where('capital', '==', true) // Query型

const SFRef = citiesRef.doc('SF') // DocumentReference型


Query

コレクションの中で、特定の条件を満たすドキュメントだけを取り出したいことや、一定の数のドキュメントを取り出したいことがあります。Queryは、その絞り込んだ結果への参照で、例えば以下のように生成します。

// capitalプロパティがtrueなドキュメントの集合への参照

const capitalsRef = citiesRef.where('capital', '==', true)

// ドキュメントの集合3つのみへの参照
const threeCitiesRef = citiesRef.limit(3)

// 人口で昇順にソートされたcitiesへの参照
const citiesSortedByPopulationRef = citiesRef.orderBy('population')

Queryの使い方は、CollectionReferenceとほとんど同じです(ドキュメントによるとCollectionReferenceQueryのサブクラスのようです)。そのため、例えばCollectionReferenceから絞り込んでQueryを生成したように、Query型をさらに絞り込んでQueryを生成することができます。以下の例では、citiesSortedByPopulationRefというQueryを絞り込んで、threeLargestCitiesRefという別のQueryを取得しています。

// 人口が多い順にソートしたcitiesへの参照

const citiesSortedByPopulationRef = citiesRef.orderBy('population', 'desc')

// もっとも人口が多い3つの都市への参照
const threeLargestCitiesRef = citiesSortedByPopulationRef.limit(3)


DocumentReference

ある一つのドキュメントの参照がDocumentReferenceです。ドキュメントが所属するコレクションを参照するCollectionReferencedocメソッドの引数にドキュメントのidを指定することで、以下のように生成できます。

const tokyoRef = citiesRef.doc('TOK')


スナップショット(QuerySnapshotQueryDocumentSnapshotDocumentSnapshot

ここまでは、あくまでデータへの"参照"を取得する話でした。firestoreに保存されているデータの内容を取得するには、"参照"をもとに、実際にデータを持っている"スナップショット"を得る必要があります。"スナップショット"について、以下で説明します。


DocumentSnapshot

DocumentSnapshotは、DocumentReferenceから得られるスナップショットで、単一のドキュメントのデータを持っています。

ここでもう一度、参照からスナップショットを得るための方法について確認しておきます。スナップショットを得るには参照に生えたget()メソッドを呼べば良いです。get()の返り値の型はスナップショットを返すPromiseです。例えば、DocumentReferenceからDocumentSnapshotを得るには以下のようにします。

const tokyoRef = citiesRef.doc('TOK')

tokyoRef.get().then(docSnapshot => {
// docSnapshotはidが'TOK'であるドキュメントのデータをもつDocumentSnapshot
// ここでdocSnapshotを使ってなんかやる
})

もちろん、async/awaitを使っても良いです。

(async () => {

const tokyoRef = citiesRef.doc('TOK')
const docSnapshot = await tokyoRef.get()
// ここでquerySnapshotを使ってなんかやる
})()

さて、実際にfirestoreに保存されているデータを取得したいときは、上記の方法で生成したDocumentSnapshotdata()メソッドを呼びます。

const tokyoRef = citiesRef.doc('TOK')

tokyoRef.get().then(docSnapshot => {
console.log(docSnapshot.data())
// { capital: true,
// country: 'Japan',
// name: 'Tokyo',
// population: 9000000,
// state: null }
})

また、特定のフィールドの値だけが欲しい時はdata()の代わりにget(<field名>)メソッドを呼びます。

const tokyoRef = citiesRef.doc('TOK')

tokyoRef.get().then(docSnapshot => {
console.log(docSnapshot.get('country'))
// Japan
})


QuerySnapshot

QuerySnapshotは、CollectionReferenceQueryから得られるスナップショットです。DocumentSnapshotが単一のドキュメントのデータを持っていたのに対して、QuerySnapshotは複数のドキュメントのデータを持つスナップショットです。QueryだけでなくCollectionReferenceからもQuerySnapshotが得られることに注意してください。

QuerySnapshotは、以下のように使います。CollectionReferenceであるcitiesRefからスナップショットを得ている例です。

citiesRef.get().then(querySnapshot => {

// ここでquerySnapshotを使ってなんかやる
})

Queryからも全く同じようにQuerySnapshotが得られます。以下の例では、querySnapshotcapitaltrueである都市のみのデータを持っています。

const capitalsRef = citiesRef.where('capital', '==', true) // capitalsRefはQuery型

capitalsRef.get().then(querySnapshot => {
// ここでquerySnapshotを使ってなんかやる
})


QueryDocumentSnapshot

複数のドキュメントのデータを持つQuerySnapshotから実際にデータを取得する際には、一つ一つのドキュメントのデータを持つQueryDocumentSnapshotに対して操作を行います。コレクションから一つ一つドキュメントを取り出して、何らかの操作を行うイメージです。具体的には、主に以下の2つの方法が取られます。

// 1. forEach

citiesRef.get().then(querySnapshot => {
querySnapshot.forEach(queryDocSnapshot => {
// ここでqueryDocSnapshotを使ってなんかやる
})
})

// 2. docs
citiesRef.get().then(querySnapshot => {
const queryDocsSnapshot = querySnapshot.docs
// ここでqueryDocsSnapshotを使ってなんかやる
})

1のqueryDocSnapshotQueryDocumentSnapshot、2のqueryDocsSnapshotQueryDocumentSnapshotの配列になっています。QueryDocumentSnapshotはほとんどDocumentSnapshotと同じように使えます。例えば、コレクションを構成するドキュメントのデータの配列が欲しいときには、data()メソッドを使って以下のようにすれば良いです。

citiesRef.get().then(querySnapshot => {

const cities = querySnapshot.docs.map(doc => doc.data())
})

また、forEachを使った例で、capitalである都市について出力したい場合は、例えば以下のようになるでしょう。

citiesRef.get().then(querySnapshot => {

querySnapshot.forEach(citySnapshot => {
const city = citySnapshot.data()
if (city.capital) {
console.log(`${city.name} is capital!`)
}
})
})
// Beijing is capital!
// Washington, D.C. is capital!
// Tokyo is capital!

QueryDocumentSnapshotDocumentSnapshotの違いについては明示的に意識しなくても良いと思いますが、例えば Firestore - Difference between DocumentSnapshot and QueryDocumentSnapshot を読むとわかると思います。


まとめ


  • firestoreに保存してあるデータを取得する時は、"参照" --get()--> "スナップショット" --data()--> "データ"という流れになる

  • 以下のクラス/インターフェースがある


    • 参照



      • CollectionReference:コレクション(or サブコレクション)への参照


      • Query:コレクションから絞り込んだ結果への参照


      • DocumentReference:ドキュメントへの参照



    • スナップショット



      • QuerySnapshotCollectionReference/Queryから得られる参照


      • QueryDocumentSnapshotQuerySnapshotの構成要素で、単一のドキュメントのデータをもつ


      • DocumentSnapshotDocumentSnapshotから得られる参照で、単一のドキュメントのデータをもつ






  • APIリファレンス を読もう