React Context搭配Apollo compose时自动重取失效的问题咨询
我太懂你这种纠结了——用React Context做组件间动作通信,结果把Context.Consumer放在组件return里后,Apollo原本的自动重取逻辑直接失效,被迫要把整个GraphQL查询移到Consumer内部手动触发重取,这完全打乱了之前的代码模式,确实够闹心的。其实你完全不用做这么大的模式改动,下面几个方案可以帮你保留Apollo的自动重取特性,同时正常使用Context:
方案1:用自定义Hook提取Context状态,作为Apollo查询的变量依赖
核心思路是把Context的状态从Consumer里抽出来,作为Apollo useQuery的变量参数。这样当Context状态变化时,Apollo会自动感知到变量变更,触发重取,完全不用手动调用refetch。
// 先写一个自定义Hook封装Context的获取 const useAppControl = () => React.useContext(AppControl); const Child = () => { // 通过Hook拿到Context里的状态和方法 const appControl = useAppControl(); // 把Context里的值作为查询变量,Apollo会自动监听变量变化 const { loading, data } = useQuery(YOUR_GQL_DOCUMENT, { variables: { // 这里用Context里的状态作为查询参数 filter: appControl.currentFilter } }); if (loading) return "..."; const { componentData } = data; return ( <React.Fragment> {/* 直接用Context的方法,动作触发后Context状态变化,Apollo自动重取 */} <button onClick={() => appControl.updateFilter("new-value")}> 更新筛选条件 </button> {/* 渲染componentData的逻辑 */} <div>{componentData.content}</div> </React.Fragment> ); };
这个方案最清爽,既保留了Apollo的自动重取,又不用嵌套Consumer,组件结构和之前用Apollo时几乎一致。
方案2:用useLazyQuery配合Context动作触发重取
如果你的重取不是由Context状态变化驱动,而是需要在用户触发某个Context动作时才执行,那可以用Apollo的useLazyQuery,在Context的回调里手动触发查询,但不用把整个查询逻辑塞进Consumer里。
const useAppControl = () => React.useContext(AppControl); const Child = () => { const appControl = useAppControl(); // 初始化lazy查询 const [fetchData, { loading, data }] = useLazyQuery(YOUR_GQL_DOCUMENT); // 组件挂载时先执行一次查询 React.useEffect(() => { fetchData(); }, []); // 监听Context里的重取信号(比如Context里维护一个shouldRefetch状态) React.useEffect(() => { if (appControl.shouldRefetch) { fetchData(); // 记得重置信号,避免无限循环 appControl.resetRefetchFlag(); } }, [appControl.shouldRefetch]); if (loading) return "..."; const { componentData } = data; return ( <React.Fragment> {/* 触发Context动作,同时设置重取信号 */} <button onClick={() => appControl.triggerActionAndMarkRefetch()}> 执行动作并重取数据 </button> {/* 渲染componentData */} </React.Fragment> ); };
这种方式适合用户主动触发的重取场景,把查询逻辑和Context动作解耦,代码结构更清晰。
方案3:用HOC封装Context,配合Apollo的compose
如果你还在使用Apollo的HOC(比如graphql)而不是Hook,可以写一个HOC把Context的值注入到组件props里,再和Apollo的HOC用compose组合,让Apollo能感知到props里的Context值变化,自动重取。
// 封装Context的HOC const withAppControl = Component => props => { const appControl = React.useContext(AppControl); return <Component {...props} appControl={appControl} />; }; const Child = ({ gqlGetData, appControl }) => { if (gqlGetData.loading) return "..."; const { componentData } = gqlGetData; return ( <React.Fragment> <button onClick={() => appControl.doAction()}>触发动作</button> {/* 渲染componentData */} </React.Fragment> ); }; // 用compose组合HOC,Apollo会监听props里的appControl变化 export default compose( withAppControl, graphql(YOUR_GQL_DOCUMENT, { name: 'gqlGetData', options: props => ({ variables: { param: props.appControl.someValue } }) }) )(Child);
这个方案适配还在使用HOC语法的老项目,避免了Consumer嵌套的问题,同时保留Apollo的自动重取能力。
核心思路总结
本质上你遇到的问题是Context Consumer嵌套在return里,导致Apollo无法感知到Context状态的变化。只要把Context的状态/值变成Apollo查询的直接依赖(不管是作为useQuery的变量,还是HOC的props参数),Apollo就能正常监听变化并自动重取,完全不用把整个查询逻辑移到Consumer内部。
内容的提问来源于stack exchange,提问作者David Thorisson

