Next.js调用router.push时Apollo useQuery非必要重取问题
调用router.push更新路由参数时,Next.js Pages Router 会触发当前页面整个组件树重渲染。Apollo Client 的useQuery Hook 默认会在重渲染过程中校验配置引用、缓存状态,如果传入的查询配置是内联写法(每次渲染生成新引用),或是没有明确指定缓存优先的拉取策略,就会在无查询变量变更的情况下误触发查询重跑,和Apollo本身“变量变更才重查”的机制不冲突,属于渲染行为+配置不当导致的异常触发。
方案1:稳定useQuery配置引用 + 明确缓存策略
这是侵入性最低的通用修复方式,核心是避免每次渲染时传入useQuery的配置对象生成新引用,同时明确告知Apollo优先读取本地缓存,不主动发起网络请求:
// 将固定的查询配置抽到组件作用域外,保证引用稳定 const queryConfig = { fetchPolicy: "cache-first", nextFetchPolicy: "cache-first", onCompleted: () => { console.log("just completed apollo query"); } } const Parent = () => { // 不要直接在useQuery参数里写内联对象,避免每次渲染生成新引用 useQuery(QUERY_HERE, queryConfig); return <Child />; }
提示:如果必须动态传入配置项,用
useMemo包裹配置对象,依赖项仅保留实际会影响查询逻辑的变量即可。
方案2:路由跳转开启浅渲染模式
如果路由参数变更仅在客户端使用,不需要触发Next.js服务端侧数据逻辑(getServerSideProps/getStaticProps重跑),可以给router.push添加shallow: true配置,路由变更只会更新URL和router对象状态,不会触发页面组件的完整重渲染流程,从根源减少不必要的渲染开销:
// 修改Child组件内的按钮点击逻辑 onClick={() => { const targetArg = currentArg === "on" ? "off" : "on"; router.push( `/?currentArg=${targetArg}`, undefined, { shallow: true } ); }}
注意:shallow模式仅对当前页面的同路径路由跳转生效,跨路径跳转时该配置不生效。
方案3:隔离路由状态的重渲染范围
可以通过React渲染边界隔离路由变化的影响范围:将依赖useRouter的逻辑封装在独立子组件中,承载useQuery的父组件用React.memo包裹,路由变化触发的重渲染只会停留在子组件层级,不会向上冒泡触发父组件重渲染,自然不会引起useQuery的异常执行:
// 父组件加memo包裹,props无变化时不会跟随子组件重渲染 const Parent = React.memo(() => { useQuery(QUERY_HERE, { onCompleted: () => { console.log("just completed apollo query"); } }); return <Child />; })
配置完成后,页面首次加载时会正常触发查询并打印日志,后续点击按钮切换路由参数时,不会重复触发查询请求,符合预期行为。
内容的提问来源于stack exchange,提问作者michaelm244

