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

