Apollo useQuery仅执行一次:组件重渲染时跳过请求的解决办法
解决Next.js + GraphQL仅获取一次数据并复用的思路
一、利用Apollo Client的缓存策略(最直接)
Apollo Client默认会缓存查询结果,通过设置fetchPolicy可以强制优先使用缓存,避免重复请求:
cache-first:优先从缓存读取数据,缓存不存在时才发起请求(推荐)cache-only:仅从缓存读取,适合确定数据已存在的场景
示例代码:
import { useQuery } from '@apollo/client'; import YOUR_QUERY from './queries/yourQuery'; function YourComponent() { const { data, loading, error } = useQuery(YOUR_QUERY, { fetchPolicy: 'cache-first', }); if (loading) return <div>加载中...</div>; if (error) return <div>出错了</div>; return <div>{JSON.stringify(data)}</div>; }
二、将查询放在不会重渲染的根组件中
Next.js的_app.js(Page Router)或layout.js(App Router)是全局根组件,只会在应用初始化或页面切换时渲染,不会因子组件重渲染而重新执行查询。可以在这里获取数据后通过props传递给子组件:
Page Router 示例(_app.js):
import { useQuery } from '@apollo/client'; import YOUR_QUERY from './queries/yourQuery'; function MyApp({ Component, pageProps }) { const { data, loading, error } = useQuery(YOUR_QUERY, { fetchPolicy: 'cache-first', }); if (loading) return <div>全局加载中...</div>; if (error) return <div>全局数据请求失败</div>; return <Component {...pageProps} sharedData={data} />; } export default MyApp;
子组件直接使用props中的sharedData即可,无需再发起查询。
三、用React Context封装全局数据
将查询逻辑封装在Context Provider中,Provider仅在初始化时执行一次查询,整个应用的组件都可以通过Context复用数据:
1. 创建DataContext:
// src/contexts/DataContext.js import { createContext, useContext } from 'react'; import { useQuery } from '@apollo/client'; import YOUR_QUERY from '../queries/yourQuery'; const DataContext = createContext(); export function DataProvider({ children }) { const { data, loading, error } = useQuery(YOUR_QUERY, { fetchPolicy: 'cache-first', }); return ( <DataContext.Provider value={{ data, loading, error }}> {children} </DataContext.Provider> ); } export function useData() { return useContext(DataContext); }
2. 在根组件中包裹Provider:
// src/pages/_app.js(Page Router) import { DataProvider } from '../contexts/DataContext'; function MyApp({ Component, pageProps }) { return ( <DataProvider> <Component {...pageProps} /> </DataProvider> ); }
3. 子组件中使用:
import { useData } from '../contexts/DataContext'; function ChildComponent() { const { data, loading, error } = useData(); if (loading) return <div>加载中...</div>; if (error) return <div>出错了</div>; return <div>{JSON.stringify(data)}</div>; }
四、利用Next.js的静态/服务器端渲染预取数据
如果数据不需要在客户端动态更新,可以在构建时(静态生成)或服务器端请求数据,直接传递给组件,客户端完全复用预取的数据:
Page Router(getStaticProps):
// src/pages/index.js import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; // 定义查询 const YOUR_QUERY = gql` query GetData { // 你的查询内容 } `; export async function getStaticProps() { // 创建临时Apollo客户端 const client = new ApolloClient({ uri: '你的GraphQL服务器地址', cache: new InMemoryCache(), }); const { data } = await client.query({ query: YOUR_QUERY }); return { props: { initialData: data }, revalidate: false, // 禁用增量静态再生,永远使用初始数据 }; } export default function Home({ initialData }) { // 直接使用预取的initialData,无需useQuery return <div>{JSON.stringify(initialData)}</div>; }
App Router(async组件):
// src/app/page.js import { ApolloClient, InMemoryCache, gql } from '@apollo/client'; const YOUR_QUERY = gql` query GetData { // 你的查询内容 } `; async function fetchInitialData() { const client = new ApolloClient({ uri: '你的GraphQL服务器地址', cache: new InMemoryCache(), }); const { data } = await client.query({ query: YOUR_QUERY }); return data; } export default async function Home() { const initialData = await fetchInitialData(); return <div>{JSON.stringify(initialData)}</div>; }
补充:关于useLazyQuery的正确用法
如果一定要用useLazyQuery,需要把触发逻辑放在useEffect中,确保仅执行一次:
import { useLazyQuery } from '@apollo/client'; import YOUR_QUERY from './queries/yourQuery'; import { useEffect } from 'react'; function YourComponent() { const [getData, { data, loading, error }] = useLazyQuery(YOUR_QUERY); useEffect(() => { getData(); // 仅在组件挂载时触发一次 }, []); // 后续渲染直接复用data if (loading) return <div>加载中...</div>; if (error) return <div>出错了</div>; return <div>{JSON.stringify(data)}</div>; }
内容的提问来源于stack exchange,提问作者user7378251
相关产品推荐
相关产品推荐

