firestoreのonSnapshotを使う際に気を付けたいこと

firestoreのコレクションに対するonSnapshotの覚書です。
#対象
firestoreにあるコレクションの追加・削除・変更をクライアントサイドにリアルタイムに反映したい場合、次のように`onSnapshot`を使って変更をリッスンすることができます。

```js:client.js
const db = firebase.firestore();

db.collection('todos')
    .onSnapshot(function (querySnapshot) {
        for (let change of querySnapshot.docChanges()) {
            if (change.type === 'added') {
                // データが追加された時
            }
            else if (change.type === 'modified') {
                // データが変更された時
            }
            else if (change.type === 'removed') {
                // データが削除された時
            }
        }
    })
```

#注意したいケース
##最初の呼び出し
[公式](https://firebase.google.com/docs/firestore/query-data/listen)にも記載されていますが、`onSnapshot`を実行すると最初に全てのドキュメントを取得します。この時、`change.type`は`added`でコールされます。これは仮にfirestoreのコレクションが空の場合でも、関数自体の呼び出しは発生します。

```js:client.js

db.collection('todos')
    .where('user', '==', user)
    .onSnapshot(function (querySnapshot) {
        for (let change of querySnapshot.docChanges()) {
            if (change.type === 'added') {
                // データが追加された時
                // もしくは最初の呼び出し
            }
            else if (change.type === 'modified') {
                // データが変更された時
            }
            else if (change.type === 'removed') {
                // データが削除された時
            }
        }
    })
```

##limitクエリとの併用
`onSnapshot`はコレクションに対するクエリと併用ができます。
特に、`limit`と`orderBy`のようなクエリを併用する場合に注意が必要です。
例えば、以下のクエリを実行するとします。

```js:client.js
// 作成日付が新しいものから5タスク購読する
db.collection('todos')
    .where('user', '==', user)
    .orderBy('createDate','desc')
    .limit(5)
    .onSnapshot(function (querySnapshot) {
        略
    })
```

firestoreには以下のデータが入っていると想定します。

```json:todos.json
{
 {"createDate":"2020/1/20", "title":"テスト1"},
 {"createDate":"2020/1/19", "title":"テスト2"},
 {"createDate":"2020/1/18", "title":"テスト3"},
 {"createDate":"2020/1/17", "title":"テスト4"},
 {"createDate":"2020/1/16", "title":"テスト5"},
 {"createDate":"2020/1/15", "title":"テスト6"},
}
```
`onSnapshot`実行直後に、テスト1〜テスト5の５つのTODOが取得できます。これは想定通りです。

```js:client.js

    .onSnapshot(function (querySnapshot) {
        for (let change of querySnapshot.docChanges()) {
            if (change.type === 'added') {
                //テスト1〜テスト5が入ってくる
            }
            略
        }
    })
```

次に新しいTODOをfirestoreに投入します。

```json:todos.json
{
 {"createDate":"2020/1/21", "title":"テスト0"},
}
```
すると、`onSnapshot`では`limit(5)`によって購読するデータの入れ替えが発生し、`change.type`の`added`と`removed`が呼び出されます。

```js:client.js

db.collection('todos')
    .where('user', '==', user)
    .orderBy('createDate','desc')
    .limit(5)
    .onSnapshot(function (querySnapshot) {
        for (let change of querySnapshot.docChanges()) {
            if (change.type === 'added') {
                // テスト0が入ってくる
            }
            else if (change.type === 'modified') {
               
            }
            else if (change.type === 'removed') {
                // テスト5が入ってくる
            }
        }
    })
```

考えてみれば、テスト０の追加によって、５つの購読対象の中からテスト５が押し出されるというのは自然なことなのですが、`limit`を使用しない時の感覚で使ってしまうと、`removed`によってあたかもstoreから削除されたと判断しかねないため、注意が必要です。

同様に、次のデータの中からテスト3を削除してみます。

```json:todos.json
{
 {"createDate":"2020/1/20", "title":"テスト1"},
 {"createDate":"2020/1/19", "title":"テスト2"},
 {"createDate":"2020/1/18", "title":"テスト3"},
 {"createDate":"2020/1/17", "title":"テスト4"},
 {"createDate":"2020/1/16", "title":"テスト5"},
 {"createDate":"2020/1/15", "title":"テスト6"},
}
```

こちらの結果はもう予想がつくかもしれませんが、テスト3が５つの購読リストから除外されたために、テスト６が代わりに購読対象に加わります。

```js:client.js

db.collection('todos')
    .where('user', '==', user)
    .orderBy('createDate','desc')
    .limit(5)
    .onSnapshot(function (querySnapshot) {
        for (let change of querySnapshot.docChanges()) {
            if (change.type === 'added') {
                // テスト6が入ってくる
            }
            else if (change.type === 'modified') {
               
            }
            else if (change.type === 'removed') {
                // テスト3が入ってくる
            }
        }
    })
```
#所感
limitクエリは、Infinite Scrollのように少しずつリッスンしたいときなどに考慮が必要かなと。