firestoreのコレクションに対するonSnapshotの覚書です。
#対象
firestoreにあるコレクションの追加・削除・変更をクライアントサイドにリアルタイムに反映したい場合、次のようにonSnapshot
を使って変更をリッスンすることができます。
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') {
// データが削除された時
}
}
})
#注意したいケース
##最初の呼び出し
公式にも記載されていますが、onSnapshot
を実行すると最初に全てのドキュメントを取得します。この時、change.type
はadded
でコールされます。これは仮にfirestoreのコレクションが空の場合でも、関数自体の呼び出しは発生します。
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
のようなクエリを併用する場合に注意が必要です。
例えば、以下のクエリを実行するとします。
// 作成日付が新しいものから5タスク購読する
db.collection('todos')
.where('user', '==', user)
.orderBy('createDate','desc')
.limit(5)
.onSnapshot(function (querySnapshot) {
略
})
firestoreには以下のデータが入っていると想定します。
{
{"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の5つのTODOが取得できます。これは想定通りです。
.onSnapshot(function (querySnapshot) {
for (let change of querySnapshot.docChanges()) {
if (change.type === 'added') {
//テスト1〜テスト5が入ってくる
}
略
}
})
次に新しいTODOをfirestoreに投入します。
{
{"createDate":"2020/1/21", "title":"テスト0"},
}
すると、onSnapshot
ではlimit(5)
によって購読するデータの入れ替えが発生し、change.type
のadded
とremoved
が呼び出されます。
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が入ってくる
}
}
})
考えてみれば、テスト0の追加によって、5つの購読対象の中からテスト5が押し出されるというのは自然なことなのですが、limit
を使用しない時の感覚で使ってしまうと、removed
によってあたかもstoreから削除されたと判断しかねないため、注意が必要です。
同様に、次のデータの中からテスト3を削除してみます。
{
{"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が5つの購読リストから除外されたために、テスト6が代わりに購読対象に加わります。
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のように少しずつリッスンしたいときなどに考慮が必要かなと。