本記事のゴール
Firestore の Query で、下記形式のデータを対象にした いいね順(スコア順)ソート & ページング を実現します
なお、記載する例は 無限スクロール 用のページングを想定しています
articles Collection
... | likeCount | createdAt | ... |
---|---|---|---|
... | 42 | 12/1 10:01 | ... |
... | 1 | 12/2 11:14 | ... |
... | 24 | 12/4 07:20 | ... |
... | 8 | 12/6 10:03 | ... |
... | 24 | 12/8 23:02 | ... |
... | 8 | 12/11 19:30 | ... |
... | 100 | 12/16 17:30 | ... |
... | 8 | 12/18 03:28 | ... |
↓ (各ページ2件 でページングする場合はこうしたい
page: 1
... | likeCount | createdAt | ... |
---|---|---|---|
... | 100 | 12/16 17:30 | ... |
... | 42 | 12/1 10:01 | ... |
page: 2
... | likeCount | createdAt | ... |
---|---|---|---|
... | 24 | 12/8 23:02 | ... |
... | 24 | 12/4 07:20 | ... |
page: 3
... | likeCount | createdAt | ... |
---|---|---|---|
... | 8 | 12/18 03:28 | ... |
... | 8 | 12/11 19:30 | ... |
page: 4
... | likeCount | createdAt | ... |
---|---|---|---|
... | 8 | 12/6 10:03 | ... |
... | 1 | 12/2 11:14 | ... |
まずは普通にページング
Firestore でのページングには 新しく追加された Date
型のフィールドを使っている方が多いのではないでしょうか。
Date
型を利用したフィールドは、管理画面上から わかりやすい表記で確認できます。
Realtime DB 時代に使っていた timestamp 値 と比較すると、わざわざ変換せずに作成日等の情報が確認できるため便利です。
Firestore での Date
型を利用したページングは非常に簡単です。
単に基準とするフィールド(今回は createdAt
)で並び替えた後、 初回なら適当に大きな Date
、2 ページ目からは 最後に取得したアイテムの値を startAfter
に渡すだけです。
const lastDate: Date = lastItem.createdAt;
const articlesRef = db.collection('articles');
articlesRef.orderBy('createdAt', 'desc').startAfter(lastDate).limit(10);
なお、startAfter
と似た startAt
というメソッドもありますが、両者の違いは渡した値を検索に含むか、含まないかです
lastDate:Date = new Date('2017 12/1 10:00');
// 検索範囲 > 2017 12/1 10:00 < 検索範囲
.orderBy('createdAt', 'asc' | 'desc').startAfter(lastDate)
// 検索範囲 ≧ 2017 12/1 10:00 ≦ 検索範囲
.orderBy('createdAt', 'asc' | 'desc').startAt(lastDate)
いいね順にソートする
ソートするだけなら簡単
const lastCount: number = lastItem.likeCount;
const articlesRef = db.collection('articles');
articlesRef.orderBy('likeCount', 'desc').limit(10);
ただこれを
articlesRef.orderBy('likeCount', 'desc').startAfter(lastCount).limit(10);
通常のページング処理と同様に行うと いいね数 が同じスコアの記事が次に検索する対象から外れてしまいます。
だからと言って startAt
を使っても、今度は最終アイテムと同じスコアが重複してしまいます。
順位がついているランキングデータではこの Query のまま使えるでしょうが、今回の要件は実現できません。
複数の OrderBy を使う
この問題を解決するために、 いいね順 及び 作成日順 でソートを行います。
これにより、いいね が同数の場合は日付を参照して次のデータを重複せずに取得することができます。
const lastDate: Date = lastItem.createdAt;
const lastCount: number = lastItem.likeCount;
const articlesRef = db.collection('articles');
lastDate.setMilliseconds(lastDate.getMilliseconds() - 1);
articlesRef.orderBy('likeCount', 'desc').orderBy('createdAt', 'desc').startAt(lastCount, lastDate).limit(10);
ポイントをまとめると・・・
- ページにまたがるアイテムのいいね数が重複することもあるので、
startAt
で最終アイテムと同じいいね数から検索開始 - 使用した
orderBy
順にstartAt
の値を指定する - 今回は更新順の取得 =>
createdAt
の値が下がっていく方向に進むので、開始日時を 1ms 減算
articles Collection
... | likeCount | createdAt | ... |
---|---|---|---|
... | 42 | 12/1 10:01 | ... |
... | 1 | 12/2 11:14 | ... |
... | 24 | 12/4 07:20 | ... |
... | 8 | 12/6 10:03 | ... |
... | 24 | 12/8 23:02 | ... |
... | 8 | 12/11 19:30 | ... |
... | 100 | 12/16 17:30 | ... |
... | 8 | 12/18 03:28 | ... |
articlesRef.orderBy('likeCount', 'desc').orderBy('createdAt', 'desc').startAt(lastCount, lastDate).limit(2)
↓
page: 1 ( likeCount: 大きな値 && createdAt: 大きな値 ~)
... | likeCount | createdAt | ... |
---|---|---|---|
... | 100 | 12/16 17:30 | ... |
... | 42 | 12/1 10:01 | ... |
↓
page: 2 ( likeCount: 42 以下のスコア && createdAt: 12/1 10:01 - 1ms 以下の時間 ~ )
... | likeCount | createdAt | ... |
---|---|---|---|
... | 24 | 12/8 23:02 | ... |
... | 24 | 12/4 07:20 | ... |
↓
page: 3 ( likeCount: 24 以下のスコア && createdAt: 12/4 07:20 - 1ms 以下の時間 ~ )
... | likeCount | createdAt | ... |
---|---|---|---|
... | 8 | 12/18 03:28 | ... |
... | 8 | 12/11 19:30 | ... |
↓
page: 4 ( likeCount: 8 以下のスコア && createdAt: 12/11 19:30 - 1ms 以下の時間 ~ )
... | likeCount | createdAt | ... |
---|---|---|---|
... | 8 | 12/6 10:03 | ... |
... | 1 | 12/2 11:14 | ... |
おわり
上記のポイントを踏まえて実装することで 冒頭で述べたいいね数順のページングが実装できます (o・∇・)o
Realtime DB の頃は、こういった処理を実現するために予めリストデータを作成しておく必要があり、それの運用・管理が大変でした…^^;
一方で Firestore を利用するとこんなに簡単に実装できてしまいます。
最高です (´▽`)
日記
firestore の記事を書く際に Firestore
タグ と CloudFirestore
タグ どっちを付けるべきなのか迷いますね…
件数が多いのは Firestore
だけど アイコンが付いてて本物っぽいのが CloudFirestore
...
う〜ん
とりあえずどっちもつけとこ^q^