Firestoreのページネーション/無限スクロール+スナップショットリスナー
18539 ワード
私は多くの開発者が彼らのアプリケーションで実装しようとしていることに気づいていました、そして、それはOnsnapshotリスナーによる反応的なページ化、すなわちバッチ/空白/ページのコレクションから文書をロードして、誰かが変化するとき、また、コレクションの文書を加えるか、削除するのを聞いていると気がつきました.
まず第一に、私が私のコードを共有する前に、これが多分あなたが一般的にしたいことでないと強調したいです.ファイアベースコンサルタントとして
Doug Stevenson said in one of his comments on stackoverflow posts
トレードオフと利用事例を理解するのは良いことです.私はPageGrid/無限のスクロールを使用して大きなフィードでリアルタイムリスニングを使用してアプリケーションを考えることはできませんし、アイテムの編集と削除のリアルタイム動作のハンドル.チャットでも、メッセージを削除すると、その状態を
しかし、理論演習のために、それを実行しようとしましょう!
これは、あなたが設定しなければならないほど完全ではない
したがって、2つのクエリが必要です.
文書の初期負荷のための1つ
以下にコードを示します:
ページの概念私は、前に用語一括/バッチを使用しました.この時点で私はこれらの'クラスタ'の名前を知らない.しかし、はい、あなたが気づくことができるように、私たちのPost Arrayは配列の配列です. 変数 リスナーを取り消すことを忘れないでください.我々の店 使用する また、使用 使用する してください、任意の質問を聞いたり、発言を追加無料!私は、すべてをはっきりと説明することができなかったかもしれません.
歓声と楽しみ
まず第一に、私が私のコードを共有する前に、これが多分あなたが一般的にしたいことでないと強調したいです.ファイアベースコンサルタントとして
Doug Stevenson said in one of his comments on stackoverflow posts
"You can't really combine paging with listening. You have to choose one or the other in order to maintain the sanity of your code."
トレードオフと利用事例を理解するのは良いことです.私はPageGrid/無限のスクロールを使用して大きなフィードでリアルタイムリスニングを使用してアプリケーションを考えることはできませんし、アイテムの編集と削除のリアルタイム動作のハンドル.チャットでも、メッセージを削除すると、その状態を
removed=true
すぐにデータベースから削除しないでください.それはあなたのアプリケーションを維持して、持っている重いものの種類です、そして、それはあなたが実際に必要とするどの機能を評価するのによいです.しかし、理論演習のために、それを実行しようとしましょう!
これは、あなたが設定しなければならないほど完全ではない
startAfter
and endAt
FireStoreカーソルを1つのバルク/バッチ/ページから別の1つの負荷を防ぐために.したがって、2つのクエリが必要です.
文書の初期負荷のための1つ
limit(PAGE_LIMIT)
(まだ必要です)startAfter
- それはquery
変数)query.limit(PAGE_LIMIT).get()
との間のドキュメントの変更をリッスン別の1つstartAfter
and endAt
変更startAfter
であるquery
変数query.endAt(newPosts[newPosts.length-1].timestamp).onSnapshot()
私は、あなたがあなたのポストの一時的なスケールを予測することができなくて、明示的なPageRain限界を無視しない限り、あなたがちょうど1つの質問でそれを作ることができると思いません.以下にコードを示します:
import { useEffect, useRef, useState } from 'react'
const PAGE_LIMIT = 5;
const useInfiniteScroll = ({ fetching: fetchingInit = false, hasMore: hasMoreInit = false, threshold = 200 }) => {
const [fetching, setFetching] = useState(fetchingInit);
const [hasMore, setHasMore] = useState(hasMoreInit);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [fetching, hasMore]);
function handleScroll() {
const offsetHeight = document.documentElement.offsetHeight, innerHeight = window.innerHeight,
scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
if (!hasMore || fetching || innerHeight + scrollTop + threshold <= offsetHeight) return;
setFetching(true);
}
return [fetching, setFetching, setHasMore];
}
const PostFeed = ({}) => {
const [pages, setPages] = useState([[]]),
[fetching, setFetching, setHasMore] = useInfiniteScroll({ fetching: true, hasMore: true }),
listeners = useRef([]);
useEffect(() => () => listeners.current.map(l => l()), []);
useEffect(() => {
if (!fetching) return;
const l = pages.length, page = pages[l - 1] || [], postLast = page[page.length - 1];
let query = firebase.firestore().collection('posts').orderBy("timestamp", "desc");
if (0 < l) query = query.startAfter(postLast?.timestamp || new Date(253402300799999));
let mounted = true;
query.limit(PAGE_LIMIT).get().then(snapshot => {
if (mounted) {
const posts = snapshot.docs.map(doc => doc.data());
setPages(ps => ps.concat([posts]));
const unsubscribe = query.endAt(posts[posts.length-1]?.timestamp || new Date()).onSnapshot(snapshot => {
const postsUpdated = snapshot.docs.map(doc => doc.data());
setPages(ps => ps.map((b, i) => i === l ? postsUpdated : b));
});
listeners.current = listeners.current.concat([unsubscribe]);
if (posts.length < PAGE_LIMIT) setHasMore(false);
setFetching(false);
}
});
return () => mounted = false;
}, [fetching]);
return (
<>
{pages.map(page => page.map(post =>
<PostCard key={post.id} post={post} />
))}
{ fetching && <SomePostSkeleton /> }
</>
)
}
export default PostFeed;
警告:new Date(253402300799999)
可能な最大日付firestore Timestamp object そして、それは9999 - 12 - 31 T 23 : 59 : 59.9999999999 zです.我々は、同じクエリを再利用し、最初のページに新しい将来の記事を聞くためにこれを使用します.私はいくつかのものを変更し、最後のポイントを見て、私たちはもうそれを必要としない0 < l
-> last(batch)?.[orderBy]
が存在しなければならない.setHasMore
であろうtrue
そして、我々はセットすることができませんfetching
to true
ということになります.useInfiniteScroll
伝統的ですuseInfiniteScroll
フック、それについて特別な何も、多分私たちがその値を設定することに注意することは重要ですfetching
すぐにPostFeed
, なぜなら、最初の負荷に対して同じ問い合わせを使用しているからです.l
の範囲内で保存されますuseEffect
したがって、バッチ/ページのドキュメントが更新されるたびに、変更は右のバッチ/ページにマップされます.useRef
だってuseState
それがレンダリングに影響を与えて、他のすべてのリターン関数で一度にすべてを取り消すならば、使われるべきですuseEffect
. mounted
順序の応答を処理する変数.つまり、firestoreの約束は未定ですが、コンポーネントはアンマウントされています.約束が終了すると、アンマウントされたコンポーネントの状態を設定すると、警告が発生します.参照https://reactjs.org/docs/hooks-faq.html#how-can-i-do-data-fetching-with-hooks を検索してください.endAt
の代わりにendBefore
だってstartAfter
前のバッチ/ページ(オープン間隔)の終了を除くendAt
現在のバッチ/ページ(閉じられた間隔)の終わりを含みます.if (0 < l)
未使用の書類も含む条件timestamp
プロパティ.私たちはすべてのドキュメントを持っているtimestamp
プロパティ.実際には、ドキュメントを更新するとき、timestamp
まだインデックスされていませんstartAfter
- endAt
間隔とは、修正されたドキュメントの迷惑フリッカーを引き起こす可能性がありますので、我々は持っていないstartAfter
最初のバッチを追加するとき.歓声と楽しみ
Reference
この問題について(Firestoreのページネーション/無限スクロール+スナップショットリスナー), 我々は、より多くの情報をここで見つけました https://dev.to/optimista/firestore-pagination-infinite-scrolling-snapshot-listeners-in-react-useeffect-3hgmテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol