You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:&nbsp; {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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:43:12