Firestore APIでのデータ取得に関わる主なクラスの関係図

FirebaseをJavaScriptで触ってみているが、DBであるFirestoreからデータを取得する段階で**何のメソッドを使えば何が返るのか**よくわからず苦労している。そこで、基本的なクラスやメソッドに絞って互いの関係を図にしてみた。

図にしてみたら大して複雑ではなかったが、メソッドなのかプロパティなのかといった微妙な違いはまだ間違えそうなので、清書して残しておく。

---

なお、私はFirebaseを触るには以下の通り非常にスキルが低い。

* 生のJavaScriptは多少扱えるが、フレームワークやトランスパイラなどは全くわからない
* 非同期処理を扱ったことが無く、 `Promise` は今回初めて知った
* さらにNoSQLも初めてで、どのようなデータ構造が良いか模索中
	* ドキュメント内にコレクションを持たせたり、フィールドのデータ型にreferenceを使ったり

## 図
文字数を減らすため記号による表現を多くしている。次節の「見方」を参照。

<!-- ![firestore-object-relations.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127130/67ebb7ef-9fa8-c2b7-90db-bb4505cdd1cb.png) -->
![firestore-object-relations.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127130/98ea202d-c877-9922-085f-ddc3caf3a45d.png)

### 見方

* 角丸四角形はクラスを表す
	* 入れ子になっているものは、内側が基本クラスで外側が派生クラス
		* 派生クラスから基本クラスのプロパティやメソッドを呼べる
		* オーバーライドは図にしていない
* 矢印はプロパティやメソッドを表す
	* 矢尻付近の文字列はその名前と*付加情報*
		* `()` 無しはプロパティ、ありはメソッド
		* `{}` ありは戻り値の型が `Promise<T>`
		* `[]` ありは戻り値の型が `T[]`
		* `[field]` はフィールドの値の参照（そのうちのreferenceデータ型のみを図示）
	* 戻り値が `null` や `undefined` である可能性は省略
	* 白い矢印は単に逆方向の参照が可能というだけ
* 上下、左右は繋がっている
	* 左右方向は、ドキュメント内にコレクションを持てるということ
	* 上下方向は、フィールドのデータ型にreferenceが使えるということ

## 補足説明
図で省略したことや読み取りにくいことについて補足する。具体的な説明用のコードは、公式ドキュメントのサンプルを基にした。

### `Firestore` からのreference取得
煩雑になるので図では省略したが、実際にコードを書くときは [**`Firestore`**](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore.html) のインスタンスが起点となる。ここからコレクションやドキュメントを取得して初めて図示した関係を使えるようになる。

`Firestore` はメソッドとして `collection()` と `doc()` の両方を持つので、コレクションとドキュメントのどちらのreferenceも取得できる[^collection_group]<sup>,</sup>[^firestore-property]。

```javascript
// firebase初期化後、Firestoreのインスタンスを取得する
firebase.initializeApp(params);
const db = firebase.firestore();

// citiesコレクションのreferenceを取得する
let citiesRef = db.collection("cities");
```

[^collection_group]: 他に `collectionGroup()` でクエリ取得もできる。
[^firestore-property]: 逆にreferenceやクエリから `Firestore` 取得もできる。プロパティ `firestore` を使う。

### クエリの設定と実行
`Query` のところに `where()` のループが描いてある。これは、**クエリ用メソッドを繋げられる**ことを示している。クエリを設定し終わったら（0個も可）、 `get()` を呼び出すことでクエリの結果である `QuerySnapshot` を得られる。

```javascript
db.collection("cities")
		.where("state", "==", "CA")
		.where("population", ">", 1000000)
		.get().then((querySnapshot) => {...});
```

クエリは `where()` 以外にも `orderBy()`, `limit()`, `startAt()` などがある。 → [クエリカーソルを使用したデータのページ設定](https://firebase.google.com/docs/firestore/query-data/query-cursors)

### `DocumentSnapshot` の取得
ドキュメントのデータを得るには `DocumentSnapshot` を得る必要がある。図の矢印が示すように2通りの方法がある。

ひとつは `DocumentReference` から直接 `get()` する方法。ドキュメントが実際には存在しなくても取得されるが、その場合は `data()` が `undefined` を返す。（ `data()` を呼ばなくても `exists` で存在確認できる）

もうひとつは `QuerySnapshot` の `docs` を参照する方法[^for-each]。クエリに該当したドキュメント全てが得られるため**配列**となっている。また、派生クラスが得られるが、これは `data()` が `undefined` を返さないことが保証されている点のみ異なる。

```javascript
// 単一のドキュメントの内容を取得する
db.collection("cities").doc("SF").get().then((doc) => {
	if (doc.exists) {
		console.log("Document data: ", doc.data());
	} else {
		// doc.data() will be undefined in this case
		console.log("No such document!");
	}
});

// コレクション内のすべてのドキュメントを取得する
db.collection("cities").get().then((querySnapshot) => {
	querySnapshot.docs.forEach((doc) => {
		// doc.data() is never undefined for query doc snapshots
		console.log(doc.id, " => ", doc.data());
	});
});
```

[^for-each]: `forEach()` もあるので、ループを回すだけなら `docs` を挟まなくてもいい。

### フィールドの値の取得
`data()` が返す `DocumentData` は、全てのフィールドをキーに持つ普通のオブジェクト。

特定のフィールドの値のみ欲しい場合は、 `DocumentSnapshot` の `get()` を使うことで `DocumentData` の取得をスキップできる。

[データ型には様々なものがサポートされている](https://firebase.google.com/docs/firestore/manage-data/data-types)。そのうちreferenceは図に描いた通り `DocumentReference` を指す。

## 参考
Firebase公式ドキュメントより

* APIリファレンス： [firestore | JavaScript SDK &nbsp;|&nbsp; Firebase](https://firebase.google.com/docs/reference/js/firebase.firestore)
* ドキュメント ガイド： [Cloud Firestore &nbsp;|&nbsp; Firebase](https://firebase.google.com/docs/firestore)