Next.js 13中React Query v4 SSR Hydration实践及问题解决
Next.js 13 App Router 数据共享与重复请求问题解决方案
针对你提到的三个核心问题,下面给出适配Next.js 13 App Router特性的具体解决方案:
1. 避免useQuery重复请求(服务端预取+脱水)
Next.js 13 App Router中,React Query的SSR需要结合服务端组件的预取能力实现脱水,让客户端直接复用服务端已获取的缓存,无需重复请求:
在根布局(app/layout.js,默认是服务端组件)中创建QueryClient并预取数据,再通过Hydrate组件将缓存传递给客户端:
// app/layout.js import { QueryClient, dehydrate } from '@tanstack/react-query'; import { QueryClientProvider, Hydrate } from '@tanstack/react-query'; import { fetchUserInfo } from '@/services/api'; export default async function RootLayout({ children }) { const queryClient = new QueryClient(); // 服务端预取数据,存入QueryClient缓存 await queryClient.prefetchQuery({ queryKey: ['userInfo'], queryFn: fetchUserInfo, }); return ( <html lang="zh-CN"> <body> <QueryClientProvider client={queryClient}> <Hydrate state={dehydrate(queryClient)}> {children} </Hydrate> </QueryClientProvider> </body> </html> ); }
客户端组件中使用useQuery时,会直接命中服务端预取的缓存,不会发起重复请求:
// app/components/UserProfile.jsx 'use client'; import { useQuery } from '@tanstack/react-query'; import { fetchUserInfo } from '@/services/api'; export default function UserProfile() { // 直接复用服务端缓存,无重复请求 const { data } = useQuery({ queryKey: ['userInfo'], queryFn: fetchUserInfo, }); return <div>用户名:{data?.name}</div>; }
2. 突破布局组件无法获取数据的限制
App Router中的布局组件(包括根布局和嵌套布局)默认是服务端组件,可以直接通过async/await在组件内部获取数据,无需依赖客户端逻辑:
// app/dashboard/layout.js export default async function DashboardLayout({ children }) { // 服务端直接获取布局所需数据 const dashboardStats = await fetch('/api/dashboard/stats').then(res => res.json()); return ( <div className="dashboard-layout"> {/* 布局自身渲染数据 */} <aside> <div>今日访问量:{dashboardStats?.todayVisits}</div> </aside> <main>{children}</main> </div> ); }
如果需要将布局获取的数据共享给子组件,结合React Context即可实现。
3. 避免Prop Drilling(全局数据共享)
无需层层传递props,可通过两种方式实现全局数据共享:
方式一:React Query全局缓存
所有组件(服务端/客户端)都可以通过queryKey直接从QueryClient缓存中获取数据,无需props传递:
// app/components/StatsCard.jsx 'use client'; import { useQuery } from '@tanstack/react-query'; import { fetchDashboardStats } from '@/services/api'; export default function StatsCard() { // 直接从缓存获取数据,无需父组件传props const { data } = useQuery({ queryKey: ['dashboardStats'], queryFn: fetchDashboardStats, }); return <div>总用户数:{data?.totalUsers}</div>; }
方式二:React Context
如果数据不需要缓存(比如布局配置),可以用Context封装,子组件通过自定义hook获取:
// app/layout.js import { createContext, useContext } from 'react'; const SiteConfigContext = createContext(); export async function RootLayout({ children }) { const siteConfig = await fetch('/api/site-config').then(res => res.json()); return ( <html lang="zh-CN"> <body> <SiteConfigContext.Provider value={siteConfig}> <header>{siteConfig?.siteName}</header> {children} </SiteConfigContext.Provider> </body> </html> ); } // 自定义hook供子组件使用 export function useSiteConfig() { return useContext(SiteConfigContext); }
子组件中直接调用hook获取数据:
// app/components/Footer.jsx import { useSiteConfig } from '../layout'; export default function Footer() { const config = useSiteConfig(); return <footer>©{new Date().getFullYear()} {config?.siteName}</footer>; }
内容的提问来源于stack exchange,提问作者Idris
相关产品推荐
相关产品推荐

