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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:31:07