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

Apollo Client从缓存取结果时不执行状态更新Hook问题求助

问题分析与解决方案

这不是Apollo Client的Bug,是你手动维护状态时的异步更新逻辑问题。

问题根源

当查询已缓存的邮编时,Apollo Client会同步返回缓存结果,立即触发onCompleted。而你在postalCodeSubmitHandler中先调用setPageState将loading设为true,这个状态更新是异步的(React会批量处理状态更新)。紧接着onCompleted里的setPageState又将loading设为false,两次状态更新被React合并后,出现了预期外的状态覆盖;再加上你使用对象式状态更新依赖当前pageState快照,进一步加剧了状态不一致的问题。

另外,你自己手动维护loading状态是冗余的——Apollo Client的useLazyQuery已经返回了内置的loading状态,它会自动处理缓存命中时的状态(缓存命中时loading直接为false)。

解决方案

方案1:使用Apollo内置的loading状态(推荐)

移除自己维护的loading字段,直接使用useLazyQuery返回的状态,彻底避免手动维护状态的问题:

const Home: NextPage = () => {
  const initialPageState: Omit<PageState, 'loading'> = {
    showPostalCodeInputForm: false,
    electionOptions: null,
    city: null,
    error: null,
  };
  const [pageState, setPageState] = useState<Omit<PageState, 'loading'>>(initialPageState);

  const GET_ELECTION = gql`
    query GetElectionForLocation($postalCode: String) {
      GetElectionForLocation(postalCode: $postalCode) {
        city
        electionOptions {
          id
          title
        }
      }
    }
  `;

  // 解构出Apollo内置的loading、data、error状态
  const [getElectionFromLocation, { loading, error: apolloError, data }] = useLazyQuery(GET_ELECTION, {
    onCompleted({ GetElectionForLocation }: GetElectionForLocationResponse) {
      setPageState({
        showPostalCodeInputForm: false,
        city: GetElectionForLocation.city,
        electionOptions: GetElectionForLocation.electionOptions,
        error: null,
      });
    },
    onError(err) {
      setPageState(prev => ({
        ...prev,
        city: null,
        electionOptions: null,
        error: err,
      }));
    },
  });

  const postalCodeSubmitHandler = (postalCode: string) => {
    setPageState(prev => ({
      ...prev,
      showPostalCodeInputForm: false,
      city: null,
      electionOptions: null,
      error: null,
    }));

    getElectionFromLocation({
      variables: { postalCode },
    });
  };

  // 渲染时直接使用Apollo的loading状态
  return (
    <>
      {loading && <div>加载中...</div>}
      {/* 其他JSX */}
    </>
  );
};

方案2:修复手动维护状态的逻辑

如果必须自己维护loading状态,需要使用函数式状态更新确保获取最新状态,同时调整onCompleted的执行逻辑:

const postalCodeSubmitHandler = (postalCode: string) => {
  // 使用函数式更新,获取最新的pageState
  setPageState(prev => ({
    ...prev,
    loading: true,
    city: null,
    electionOptions: null,
  }));

  getElectionFromLocation({
    variables: { postalCode },
  });
};

// 同时修改onCompleted,也使用函数式更新(可选,但更安全)
const [getElectionFromLocation] = useLazyQuery(GET_ELECTION, {
  onCompleted({ GetElectionForLocation }: GetElectionForLocationResponse) {
    setPageState(prev => ({
      ...prev,
      loading: false,
      showPostalCodeInputForm: false,
      city: GetElectionForLocation.city,
      electionOptions: GetElectionForLocation.electionOptions,
      error: null,
    }));
  },
  onError(apolloError) {
    setPageState(prev => ({
      ...prev,
      loading: false,
      city: null,
      electionOptions: null,
      error: apolloError,
    }));
  },
});

方案3:调整Apollo的fetchPolicy

如果你想保留缓存,同时确保每次查询都触发onCompleted并更新状态,可以使用cache-and-network策略——它会先返回缓存结果,再后台请求最新数据更新缓存:

const [getElectionFromLocation] = useLazyQuery(GET_ELECTION, {
  fetchPolicy: 'cache-and-network',
  // 其他配置不变
});

总结

Apollo Client的缓存行为是预期的,问题出在手动维护状态时的异步更新逻辑。推荐使用方案1,直接利用Apollo内置的状态管理能力,减少手动维护的冗余和出错概率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 04:40:23