typescript 如何使用React Query高效地轮询数据

r55awzrz  于 12个月前  发布在  TypeScript
关注(0)|答案(1)|浏览(164)

我有一个数据库集合,可以由大量用户实时更新。用户需要能够看到其他成员的更新,但集合很大,所以每次重新下载整个集合是非常低效/昂贵的。
每个文档都有一个updateTime时间戳。考虑到这一点,我想做的是根据需要轮询更新的数据(无论是在组件挂载时还是以其他频率),并使用ReactQuery(使用persistClientQuery)将其合并到已经存储在缓存中的数据中。
我是ReactQuery的新手,所以我想知道是否有比我在这里使用的方法更有效的方法,我使用React Query的useInfiniteQuery钩子,最新的updateTime作为nextPageParam

查询本身:

export function useTalentWithStore() {
    const queryClient = useQueryClient();

    const query = useInfiniteQuery<Talent[]>({
        queryKey: ['talent'],
        getNextPageParam: (lastPage, allPages) => {
            const data = allPages.flat();

            // Use the newest document's updateTime as the next page param
            const times = data.map((tal) => tal?.docMeta?.updateTime);
            if (data.length) {
                const max = Math.max(...times as any);
                return new Date(max);
            }
            return undefined;
        },
        queryFn: async ({ pageParam }) => {
            let talentQuery: firebase.firestore.CollectionReference<firebase.firestore.DocumentData> | firebase.firestore.Query<firebase.firestore.DocumentData>
                = firestore.collection("talent");

            // If the there's a page param, just get documents updated since then, otherwise, get everything
            if (pageParam) {
                talentQuery = talentQuery.where("docMeta.updateTime", ">", pageParam);
            }
            let talentSnapshot = await talentQuery.get();

            const talentUpdates: Talent[] = talentSnapshot.docs.map((doc) => {
                return {
                    id: doc.id,
                    ...doc.data()
                }
            });
            return talentUpdates;
        },
        staleTime: Infinity,
    });

    // Combine new data with any old data, and return a flat object 
    const flatData = useMemo<Talent[] | undefined>(() => {
        const oldData = query.data?.pages?.[0] || [];
        const newData = query.data?.pages?.[1] || [];

        const combinedData: Talent[] = [];
        if (oldData) {
            combinedData.push(...oldData);
        }
        for (const tal of newData) {
            const idx: number = combinedData.findIndex((t) => t.id === tal.id);
            if (idx >= 0) {
                combinedData[idx] = tal;
            } else {
                combinedData.push(tal);
            }
        }

        // If there's any old data, flush it out and replace it with the combined new data
        if (oldData.length) {
            queryClient.setQueryData(['talent'], (data: any) => ({
                pages: [combinedData],
                pageParams: query.data?.pageParams,
            }));
        }

        return combinedData;
    }, [query.data, queryClient]);

    return { ...query, flatData };
}

字符串

用法示例:

const talentQuery = useTalentWithStore();
const talent = talentQuery.flatData;
const [fetchedOnMount, setFetchedOnMount] = useState(false);

useEffect(() => {
    if (!fetchedOnMount && !talentQuery.isFetching) {
        console.log(`Fetching New Talent`, !fetchedOnMount && !talentQuery.isFetching);
        talentQuery.fetchNextPage();
    }
    setFetchedOnMount(true);
}, [talentQuery, fetchedOnMount]);


这一切真的有必要吗?或者ReactQuery本身就支持这个功能吗?
如果没有,我是否应该考虑其他方法或需要注意的陷阱?
(Note:虽然这段代码使用了Firestore,但由于各种原因,我不想在这里使用Firestore的实时更新)

zkure5ic

zkure5ic1#

把这个献给其他沿着蹒跚而行的人!
有一个更简单的解决方案,基本上是使用普通的query并将queryClient传递到queryFn中,这样您就可以使用它来获取任何旧数据并使用最近记录的日期/时间过滤查询。
如果您将staleTime设置为Infinity(如前所述),并将refetchOnMount设置为always,则将获得一个查询,该查询保存以前的结果,每当查询重新装载时获取任何更新,并使用您指定的任何合并函数合并这些更新。

// Generic Query Function
async function getFreshCollection<T extends Doc>(queryKey: QueryKey, queryClient: QueryClient, collectionName: string) {
    // Use the old data to find the latest timestamp
    const oldData: Doc[] = queryClient.getQueryData(queryKey) || [];

    // This function can be whatever you need; see the question for my example
    const updatedSince = findLargestTimestamp(oldData);

    // Set up the query
    let firebaseQuery: firebase.firestore.CollectionReference<firebase.firestore.DocumentData> | firebase.firestore.Query<firebase.firestore.DocumentData>
        = firestore.collection(collectionName);
    
    // If the there's an updatedSince, add that to the query
    if (updatedSince) {
        firebaseQuery = firebaseQuery.where("docMeta.updateTime", ">", updatedSince);
    }

    // Actually run the query and map the data
    const snapshot = await firebaseQuery.get();
    const newData = snapshot.docs.map((doc) => {
        return { id: doc.id, ...doc.data() } as T
    });
    
    // Merge the new data with the old as needed; Simplest would be to just append the new data
    // But in my case updates could be a combo of some updates to previously fetched docs and some net new documents, 
    // so I need to overwrite old docs with newer updates and add in the new ones (see question for an example)
    return flattenData<T>(oldData, newData);
}

// Usage
export function useTalent() {
    const queryKey = ['talent'];
    const collectionName = 'talent';
    const queryClient = useQueryClient();

    const query = useQuery({
        queryKey: queryKey,
        queryFn: async () => getFreshCollection<Talent>(queryKey, queryClient, collectionName),
        staleTime: Infinity,
        refetchOnMount: 'always',
        onError: (err) => handleFreshQueryError(err, queryKey)
    });

    return query;
}

字符串

相关问题