Electron中使用Apollo Client出现useQuery重复请求问题
解决Electron中Apollo Client多重复请求、缓存失效问题
核心问题分析
- Apollo Client实例重复创建:你的
useClient钩子每次调用都会生成全新的ApolloClient和InMemoryCache实例,一旦父组件重渲染,就会创建新客户端,之前的缓存完全失效,导致所有查询重复发起。 - 相同__typename的缓存冲突:两个查询返回的父对象
__typename相同,但无唯一标识字段,Apollo无法区分两个缓存条目,导致缓存覆盖或无法正确存储,进而触发重复请求。
解决方案
1. 缓存Apollo Client实例,避免重复创建
修改useClient,用useMemo缓存客户端实例,仅当authToken变化时重新创建:
import { useMemo } from 'react'; import { ApolloClient, createHttpLink, InMemoryCache, setContext } from '@apollo/client'; export const useClient = () => { const authToken = sessionStorage.getItem('token'); const client = useMemo(() => { const httpLink = createHttpLink({ uri: 'https://api', }); const authLink = setContext((_, { headers }) => ({ headers: { ...headers, authorization: authToken ? `Bearer ${authToken}` : '', }, })); return new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(), }); }, [authToken]); // 仅token变化时重建客户端 return { client }; };
2. 解决相同__typename的缓存冲突
针对两个查询返回相同__typename的问题,有两种处理方式:
方式一:自定义缓存ID生成逻辑
在InMemoryCache中配置dataIdFromObject,为相同__typename的对象生成唯一标识:
const cache = new InMemoryCache({ dataIdFromObject: (object) => { // 假设你的共享__typename是"SharedType",结合查询相关的唯一字段生成ID if (object.__typename === 'SharedType') { // 如果返回结果中有区分字段(比如queryType),用它来生成唯一ID return `${object.__typename}:${object.queryType}`; } // 默认逻辑:用__typename+id生成ID,无id时用随机值临时区分 return object.id ? `${object.__typename}:${object.id}` : `${object.__typename}:${Math.random()}`; }, });
方式二:为单个查询指定缓存键
在useQuery中通过context给每个查询设置唯一缓存键,让Apollo区分不同查询的结果:
useGetUserQuery({ variables: { id: 'SE' }, context: { cache: { key: 'getUser-SE', // 唯一缓存键 }, }, fetchPolicy: 'cache-first', // 强制优先使用缓存 }); useTagsQuery({ context: { cache: { key: 'tags-query', // 唯一缓存键 }, }, fetchPolicy: 'cache-first', });
3. 验证组件挂载情况
检查Test组件是否被多次挂载(比如父组件逻辑导致重复渲染/挂载),可以添加挂载日志确认:
const Test = () => { console.log('rendering ....'); useEffect(() => { console.log('Test component mounted'); return () => console.log('Test component unmounted'); }, []); useGetUserQuery({ variables: { id: 'SE' } }); useTagsQuery(); return null; };
如果出现多次挂载/卸载,需要排查父组件的渲染逻辑。
验证结果
修改后,重新运行项目:
- 网络面板应只出现2次请求(对应两个useQuery)
- Apollo DevTools缓存面板应能看到两个查询的缓存条目,不再出现覆盖情况
内容的提问来源于stack exchange,提问作者Marek Kostruski
相关产品推荐
相关产品推荐

