NextJS中如何等待useQuery数据加载完成后再处理?
React Query 数据加载后处理逻辑的最佳实践
核心问题拆解
你遇到的问题本质是React Query数据流的时机控制:组件渲染时data初始为undefined,直接在渲染外部打印或执行逻辑会拿到空值;同时纠结格式化、额外API调用的逻辑应该放在哪里。
解决方案分场景说明
1. 纯数据格式化(无额外API调用):直接放在useQuery查询函数中
这是最优解,useQuery的查询函数就是用来处理「数据获取+转换」的,返回格式化后的数据,React Query会自动缓存处理结果,避免重复计算。
修正后的useQuery代码:
const { data: processedData, status } = useQuery(['firestoreData'], async () => { // 1. 获取原始数据 const q = query(collection(db, loc)); const snapshot = await getDocs(q); const rawDocs = snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id // 带上文档ID,后续处理用 })); // 2. 计算平均价格Map(纯数据转换,不操作组件状态) const avgMap = new Map(); rawDocs.forEach(doc => { const ticker = doc.stockTicker; if (avgMap.has(ticker)) { const existing = avgMap.get(ticker); const oldTotal = parseFloat(existing.shares) * parseFloat(existing.price); let newShares, newPrice; if (doc.type === "buy") { newShares = parseFloat(existing.shares) + parseFloat(doc.shares); newPrice = (oldTotal + parseFloat(doc.shares) * parseFloat(doc.price)) / newShares; } else { newShares = parseFloat(existing.shares) - parseFloat(doc.shares); newPrice = (oldTotal - parseFloat(doc.shares) * parseFloat(doc.price)) / newShares; } avgMap.set(ticker, { ...existing, shares: newShares, price: newPrice }); } else { avgMap.set(ticker, doc); } }); // 返回原始数据+处理后的Map,方便组件直接使用 return { rawDocs, avgMap }; });
2. 依赖数据的额外API调用(比如查询股价):用第二个useQuery
利用React Query的依赖查询特性,当第一个请求完成后再触发第二个请求,借助enabled选项控制执行时机,还能享受缓存、自动重试等功能。
示例代码:
// 当第一个查询的processedData存在时,触发股价查询 const { data: stockPrices } = useQuery( ['stockPrices', processedData?.avgMap], // 查询键包含依赖,数据变化时自动刷新 async () => { if (!processedData) return {}; const priceMap = {}; // 并行调用所有股价API,提升性能 const pricePromises = Array.from(processedData.avgMap.keys()).map(async (ticker) => { const price = await getStockPrice(ticker); priceMap[ticker] = parseFloat(price.toFixed(2)); }); await Promise.all(pricePromises); return priceMap; }, { enabled: !!processedData } // 只有当processedData存在时才执行 );
3. 组件内渲染与状态更新
在组件中根据status判断加载状态,直接使用处理后的数据:
// 加载中状态 if (status === 'loading') return <div>加载中...</div>; // 错误状态 if (status === 'error') return <div>数据加载失败,请重试</div>; // 数据就绪后,组装最终渲染数据 const finalRenderData = Array.from(processedData.avgMap.entries()).map(([ticker, data]) => { const currentPrice = stockPrices?.[ticker]; if (!currentPrice) return null; const profitLoss = (parseFloat(data.shares) * (currentPrice - parseFloat(data.price))).toFixed(2); const totalEquity = (parseFloat(data.shares) * currentPrice).toFixed(2); return { ticker, shares: data.shares, averagePrice: parseFloat(data.price).toFixed(2), currentPrice, profitLoss, totalEquity, dividendYield: "Coming Soon" }; }).filter(Boolean); // 直接渲染finalRenderData return ( <div> {finalRenderData.map(item => ( <div key={item.ticker}> <h3>{item.ticker}</h3> <p>平均价格: {item.averagePrice}</p> <p>当前价格: {item.currentPrice}</p> <p>盈亏: {item.profitLoss}</p> </div> ))} </div> );
4. 若必须使用useEffect:严格控制触发时机
如果你的逻辑必须依赖组件状态更新(比如本地状态管理),可以用useEffect,但一定要:
- 将
data和status加入依赖数组 - 先判断数据是否就绪(
status === 'success'且data存在) - 避免重复执行(比如用ref标记是否已处理过)
修正后的useEffect代码:
const dataProcessedRef = useRef(false); useEffect(() => { // 用户未登录时跳转 if (!currentUser) { router.push('/login'); return; } // 数据未加载完成,直接返回 if (status !== 'success' || !processedData || dataProcessedRef.current) return; dataProcessedRef.current = true; // 标记已处理,避免重复执行 const processData = async () => { const { avgMap } = processedData; const stockPrices = {}; // 并行获取股价 await Promise.all(Array.from(avgMap.keys()).map(async (ticker) => { stockPrices[ticker] = await getStockPrice(ticker); })); // 组装最终数据并更新组件状态 const finalData = Array.from(avgMap.entries()).map(([ticker, data]) => { const currentPrice = parseFloat(stockPrices[ticker].toFixed(2)); const profitLoss = (parseFloat(data.shares) * (currentPrice - parseFloat(data.price))).toFixed(2); return { ticker, shares: data.shares, averagePrice: parseFloat(data.price).toFixed(2), currentPrice, profitLoss, dividendYield: "Coming Soon" }; }); setListDocuments(finalData); // 其他状态更新... }; processData(); }, [currentUser, status, processedData, router]);
关键注意事项
- 不要在组件渲染外部直接打印
data:组件会多次渲染,打印时机不受控,要在status === 'success'后再打印。 - 尽量避免用useEffect处理数据转换:useQuery的查询函数和依赖查询是更符合React Query设计的方式,能减少状态管理复杂度。
- 利用React Query的缓存:相同查询键的请求会直接用缓存,避免重复请求和计算。
内容的提问来源于stack exchange,提问作者Dotman
相关产品推荐
相关产品推荐

