FirestoreをデータストアとしたNext.jsのアプリケーションを作ってみています。Firestoreのあるコレクションのデータを一覧表示したときに、ページネーション機能が必要だなと思い、作ってみようとしたところ、思いの外苦戦したので、実現方法を纏めておきます。
まずはじめにFirestoreには、SQLでいうところのLIMIT
はあるのですが件数を基準にしたOFFSET
に相当する機能がないので、
[<< ] [<] [1] [2] [3] ... [>] [>>]
このようなページネーションは簡単にはできなさそうです。
代わりにクエリカーソルを使ってのページ設定というのが可能なので、
[< prev] [next >]
このようなページネーションであれば実現できそうです。 クエリカーソルを使ったページ設定では、具体的には、
と件数を指定することで、取得したいデータの範囲を絞ってページネーションを実現することになります。
クエリの開始点を指定するには、startAt()
かstartAfter()
メソッドを使用します。
両者の違いは、引数に指定した点を含めるか否か、で、後者の場合は指定した点の次のデータからが取得範囲になります。
クエリの終了点を指定するには、endAt()
かendBefore()
メソッドを使用します。
開始点と同じように両者の違いは指定した点を含めるか否か、で、後者の場合は指定した点の前のデータからが取得範囲になります。
取得したいデータの件数を指定するには、limit()
かlimitToLast()
メソッドを使用します。
limit()
はクエリにヒットするデータの先頭から指定した件数だけを取得できます。
limitToLast()
はクエリにヒットするデータの末尾から指定した件数だけを取得できます。
したがって、実際のページネーションは上記を組み合わせて実現します。 具体的には、次のような形になります。
先頭ページを取得する場合は、クエリーカーソルは使いません。 このような関数でデータが取得できます。
const getSnapshot = async (perPage: number): Promise<firebaseClient.firestore.QuerySnapshot<FirebaseFirestore.DocumentData>> => {
let query: firebaseClient.firestore.Query<FirebaseFirestore.DocumentData> = firestore.collection(collectionName).orderBy(sortField);
query = query.limit(perPage);
return await query.get();
}
ここで、collectionName
はFirestoreのコレクションの名称、perPage
は1ページに含めるデータの最大数、sortField
はデータを並び替えるフィールドの名前になります。
つまり、collectionName
で指定したコレクションを、sortField
でソートして、先頭から最大perPage
件取得することになります。
とあるページを表示中に、そのページの次のページを取得する場合のクエリは次のようになります。
const getNextSnapshot = async (start: string, perPage: number): Promise<firebaseClient.firestore.QuerySnapshot<FirebaseFirestore.DocumentData>> => {
let query: firebaseClient.firestore.Query<FirebaseFirestore.DocumentData> = firestore.collection(collectionName).orderBy(sortField);
query = query.startAfter(start).limit(perPage);
return await query.get();
}
次ページを取得する場合は、先程出てきたstartAfter()
とlimit()
を使います。
startAfter()
には、現在表示中のページの最後のデータの値、orderBy()
に指定したフィールドの値を指定します。上のコードでは、start
パラメーターがそれに当たります。startAfter()
で指定した開始点から最大でlimit()
で指定した件数取得する、という動きになります。
とあるページを表示中に、そのページの前のページを取得する場合のクエリは次のようになります。
const getPrevSnapshot = async (end: string, perPage: number): Promise<firebaseClient.firestore.QuerySnapshot<FirebaseFirestore.DocumentData>> => {
let query: firebaseClient.firestore.Query<FirebaseFirestore.DocumentData> = firestore.collection(collectionName).orderBy(sortField);
query = query.endBefore(end).limitToLast(perPage);
return await query.get();
}
次ページを取得する場合は、先程出てきたendBefore()
とlimitToLast()
を使います。
endBefore()
には、現在表示中のページの先頭のデータの値、orderBy()
に指定したフィールドの値を指定します。上のコードでは、end
パラメーターがそれに当たります。endBefore()
で指定した終了点から最大でlimitToLast()
で指定した件数取得する、という動きになります。
ここで、limitToLast()
ではなくlimit()
を使ってしまうと、終了点を指定しても先頭データからデータを取得してしまうことになり、期待通りのデータが取得できないので注意が必要です。