Firestore useCollectionData重复请求问题:如何实现单次请求与缓存?
解决多次请求问题:单次请求与缓存实现方案
问题根源
每个PsaInfoCell组件实例都会独立调用usePsaConnections,导致相同查询被重复发起(20次),进而触发请求限制。核心是要实现数据共享,确保同一查询条件下只发起一次请求。
方案一:父组件统一请求(最直接高效)
将usePsaConnections的调用提升到表格的父组件中,通过props把数据传递给所有PsaInfoCell,实现全局单次请求。
修改父组件
// 表格父组件示例 export const TicketTable = () => { const [psaConnections, isLoading] = usePsaConnections() return ( <Table> {ticketList.map(ticket => ( <TableRow key={ticket.id}> <PsaInfoCell psaInfo={ticket.psaInfo} eventId={ticket.eventId} psaConnections={psaConnections} isLoading={isLoading} /> </TableRow> ))} </Table> ) }
修改PsaInfoCell组件
移除内部Hook调用,改为接收props:
export const PsaInfoCell: FC<{ psaInfo?: PsaInfo[] eventId?: string psaConnections?: IPsaConnection[] isLoading: boolean }> = ({ psaInfo, eventId, psaConnections, isLoading }) => { const { loading: isCreationLoading } = useProcessPsaTicket() const psaTypes = useMemo<PSA_TYPE[] | undefined>(() => { return queuedPsaTypes(psaInfo, psaConnections) }, [isLoading, psaConnections, psaInfo]) // 其余原有渲染逻辑保持不变 if (!psaInfo || (psaTypes && !psaTypes?.length) || !eventId) { return <></> } return ( <> <div> <div className="flex flex-wrap mt-1 text-xs text-gray-500 max-w-xs"> <div className="text-gray-900"> Queued tickets: {isLoading && <CurrentLoader />} </div> {!isLoading && psaTypes && psaTypes.map((psaType, i) => ( <div key={i} className="flex break-all whitespace-pre"> {psaType} {isCreationLoading(psaType, eventId) && ( <LoaderWithTooltip size={4} tooltipContent={<ul>Ticket creation in progress</ul>} /> )} {i !== psaTypes.length - 1 ? ', ' : ''} </div> ))} </div> </div> </> ) }
方案二:自定义Hook内部实现缓存(适合无法提升组件的场景)
在usePsaConnections中加入缓存逻辑,确保同一partnerId下只发起一次请求,同时修复原Hook的依赖错误(原useMemo依赖db无效,应依赖partnerRef)。
import { useRef, useEffect } from 'react' export const usePsaConnections = () => { const { userData } = useAuth() const partnerRef = userData?.partner // 缓存查询结果与加载状态 const cachedData = useRef<{ data: IPsaConnection[] | undefined; loading: boolean }>({ data: undefined, loading: true }) // 记录当前partnerId,避免重复请求 const currentPartnerId = useRef<string | undefined>(partnerRef?.id) // 修正依赖:仅在partnerRef变化时重新生成查询 const query = useMemo(() => { if (!partnerRef) return null const psaConnectionsRef = db.collection('psaConnections') return psaConnectionsRef .where('partnerId', '==', partnerRef.id) .where('type', 'in', [ PSA_TYPE.CONNECTWISE, PSA_TYPE.AUTOTASKPSA, PSA_TYPE.KASEYA, ]) }, [partnerRef]) const [psaConnections, isLoading] = useCollectionData<IPsaConnection>(query, { idField: 'id', }) // 更新缓存 useEffect(() => { if (!isLoading) { cachedData.current = { data: psaConnections, loading: false } currentPartnerId.current = partnerRef?.id } }, [isLoading, psaConnections, partnerRef?.id]) // 若当前partnerId与缓存一致,直接返回缓存数据 if (partnerRef?.id === currentPartnerId.current && !isLoading) { return [cachedData.current.data, cachedData.current.loading] } return [psaConnections, isLoading] }
方案三:全局状态管理(适合跨组件共享数据)
如果psaConnections需要在多个页面/组件中使用,可借助Zustand、Redux等状态管理库,将数据存入全局状态,仅初始化时请求一次。
以Zustand为例创建全局Store
// store/psaStore.ts import { create } from 'zustand' import { db } from '../firebase' import { PSA_TYPE, IPsaConnection } from '../types' interface PsaStore { psaConnections: IPsaConnection[] | undefined isLoading: boolean fetchPsaConnections: (partnerId: string) => void } export const usePsaStore = create<PsaStore>((set) => ({ psaConnections: undefined, isLoading: false, fetchPsaConnections: async (partnerId) => { set({ isLoading: true }) const query = db.collection('psaConnections') .where('partnerId', '==', partnerId) .where('type', 'in', [ PSA_TYPE.CONNECTWISE, PSA_TYPE.AUTOTASKPSA, PSA_TYPE.KASEYA, ]) const snapshot = await query.get() const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })) as IPsaConnection[] set({ psaConnections: data, isLoading: false }) } }))
在组件中使用全局状态
// 初始化时调用请求(如App组件或表格父组件) export const App = () => { const { userData } = useAuth() const { fetchPsaConnections } = usePsaStore() useEffect(() => { if (userData?.partner?.id) { fetchPsaConnections(userData.partner.id) } }, [userData?.partner?.id, fetchPsaConnections]) return <>{/* 渲染子组件 */}</> } // PsaInfoCell直接读取全局状态 export const PsaInfoCell: FC<{ psaInfo?: PsaInfo[] eventId?: string }> = ({ psaInfo, eventId }) => { const { psaConnections, isLoading } = usePsaStore() const { loading: isCreationLoading } = useProcessPsaTicket() const psaTypes = useMemo<PSA_TYPE[] | undefined>(() => { return queuedPsaTypes(psaInfo, psaConnections) }, [isLoading, psaConnections, psaInfo]) // 其余渲染逻辑保持不变 }
内容的提问来源于stack exchange,提问作者Oleksandr Dimov
相关产品推荐
相关产品推荐

