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

条件调用Apollo useQuery首次请求返回异常结果排查

问题根因

首次触发拿不到全量数据是三个逻辑问题叠加导致的:

  • 异步时序问题:getAll里调用的setAll是异步方法,点击触发时只会把all状态改成true,当前同步执行的代码里拿到的items还是旧的当月数据,同时useQuery切换到全量查询后需要走网络请求,不可能立刻返回结果。
  • 数据判断逻辑漏洞:useEffect里没有根据当前的all状态匹配对应的返回字段,两个if是并行执行的。Apollo Client切换查询时不会自动清空result.data上的旧字段,全量请求第一次返回时,result.data会同时存在旧的当月数据字段getCurrentMonth和新的全量字段getItems,很容易出现旧数据先入为主覆盖新值的情况,再加上React严格模式下useEffect会双调用,这个问题会更明显。
  • 缺少完成态通知:getAll方法只做了状态切换,没有等全量数据请求、写入store完成就返回,调用方如果在调用getAll后立刻读取items,拿到的必然是旧值。

另外代码里还有两个隐性问题:一是useReducer初始值传了空数组,但reducer返回的是带items属性的对象,首次渲染时state.items是undefined;二是组件顶层定义的items局部变量从来没被用到,属于完全无效的冗余代码。

修复方案

1. 修正基础逻辑错误

补全缺失的ItemContext创建、dayjs导入、useItem自定义Hook实现,把useReducer的初始值改成和reducer返回结构一致的{ items: [] },删掉没用的局部items赋值代码。

2. 优化useEffect更新逻辑

把all加入useEffect依赖数组,用互斥判断根据当前查询模式读取对应字段,避免旧字段干扰,同时给useQuery加fetchPolicy: 'network-only'配置,切换查询时不读取旧缓存。

3. 改造getAll为异步方法

让getAll在触发全量查询后,等数据完全写入store再返回,调用方可以await等待数据准备完成后再执行导出操作。

修复后的核心代码如下:

ItemsContext 代码

import React, {
  useState,
  useReducer,
  createContext,
  useContext,
  useEffect,
  useCallback,
} from 'react'
import { useQuery } from '@apollo/client'
import dayjs from 'dayjs'
import { CURRENT_MONTH, ALL_ITEMS } from '../graphql/queries'
import itemReducer from '../reducers/itemReducer'

const ItemContext = createContext()

export const ItemProvider = ({ children }) => {
  const [all, setAll] = useState(false)
  const selectedMonth = dayjs(new Date()).format('YYYY-MM')
  // 修正reducer初始值结构
  const [state, dispatch] = useReducer(itemReducer, { items: [] })

  const result = useQuery(all ? ALL_ITEMS : CURRENT_MONTH, {
    variables: all ? null : { selectedMonth },
    fetchPolicy: 'network-only'
  })

  useEffect(() => {
    // 全量模式只读取getItems字段
    if (all && result.data?.getItems) {
      dispatch({
        type: 'ALL',
        items: result.data.getItems,
      })
      return
    }
    // 当月模式只读取getCurrentMonth字段
    if (!all && result.data?.getCurrentMonth) {
      dispatch({
        type: 'ALL',
        items: result.data.getCurrentMonth,
      })
    }
  }, [result.data, all])

  const getAll = useCallback(async () => {
    if (all) return
    setAll(true)
    // 等待全量数据返回并写入store
    await result.refetch()
    // 等待dispatch生效
    await new Promise(resolve => setTimeout(resolve, 0))
  }, [all, result])

  const value = {
    items: state.items,
    getAll,
    loading: result.loading
  }
  return <ItemContext.Provider value={value}>{children}</ItemContext.Provider>
}

export default function useItem() {
  return useContext(ItemContext)
}

ExportData 组件代码

import useItem from '../../context/ItemsContext'

const ExportData = () => {
  const { items, getAll } = useItem()

  const dialogConfirmation = async confirm => {
    if (confirm) {
      await getAll()
      // 等待getAll执行完成后再执行导出逻辑,此时items已是全量数据
      // 编写对应导出逻辑即可
    }
  }

  return <button onClick={() => dialogConfirmation(true)}>导出全量数据</button>
}

改完之后首次点击就可以正常拿到全量数据,不需要点第二次。

内容的提问来源于stack exchange,提问作者smoothlikebutter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 04:21:34