Next.js 13 SSG/SSR场景下如何避免Props Drilling传递API数据?
解决Next.js中SSG/SSR场景下的Props Drilling问题
针对你遇到的逐层传递props的问题,下面提供三种实用方案,覆盖轻量到中大型项目的不同需求:
方案一:React Context(轻量无额外依赖)
适合不想引入第三方状态库的场景,通过Context将数据全局共享,嵌套组件直接消费。
步骤1:创建Product Context
新建ProductContext.tsx:
'use client' import { createContext, useContext, ReactNode } from 'react' import { Product } from './product.types' type ProductContextType = { products: Product[] } const ProductContext = createContext<ProductContextType | undefined>(undefined) export function ProductProvider({ children, products }: { children: ReactNode, products: Product[] }) { return ( <ProductContext.Provider value={{ products }}> {children} </ProductContext.Provider> ) } export function useProducts() { const context = useContext(ProductContext) if (!context) throw new Error('useProducts必须在ProductProvider内部使用') return context }
步骤2:修改Page组件(Server Component)
在页面层获取数据后,用Provider包裹子组件:
import CompA from "./CompA"; import { ProductProvider } from './ProductContext' const apicall = async () => { const res = await fetch("https://fakestoreapi.com/products", { next:{ revalidate: 10 } }); return await res.json() } const Page = async () => { const data = await apicall() return ( <ProductProvider products={data}> <CompA /> </ProductProvider> ) } export default Page
步骤3:简化嵌套组件
CompA无需再传递props:
'use client' import React from 'react' import CompB from './CompB' const CompA = () => { return <CompB /> } export default CompA
CompB直接通过hook获取数据:
'use client' import React from 'react' import Image from 'next/image'; import { Product } from './product.types'; import { FaStar } from "react-icons/fa"; import { useProducts } from './ProductContext' const CompB = () => { const { products } = useProducts() return ( <div className="products-grid"> {products.map(product => ( <div key={product.id} className="product-card"> <h3>{product.title}</h3> <Image src={product.image} alt={product.title} width={200} height={200} /> <div className="rating"> {Array(Math.floor(product.rating.rate)).fill(0).map((_, i) => ( <FaStar key={i} /> ))} <span>{product.rating.count}条评价</span> </div> </div> ))} </div> ); }; export default CompB
方案二:Redux Toolkit(RTK)
适合中大型项目,利用RTK的状态管理能力统一维护数据,尤其推荐用RTK Query处理API请求(内置缓存、重验证)。
步骤1:安装依赖
npm install @reduxjs/toolkit react-redux
步骤2:配置RTK Store和API切片
新建store.ts:
import { configureStore } from '@reduxjs/toolkit' import { setupListeners } from '@reduxjs/toolkit/query/react' import { productsApi } from './productsApi' export const store = configureStore({ reducer: { [productsApi.reducerPath]: productsApi.reducer, }, middleware: (getDefault) => getDefault().concat(productsApi.middleware), }) setupListeners(store.dispatch) export type RootState = ReturnType<typeof store.getState> export type AppDispatch = typeof store.dispatch
新建productsApi.ts:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' import { Product } from './product.types' export const productsApi = createApi({ reducerPath: 'productsApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://fakestoreapi.com/' }), endpoints: (builder) => ({ getProducts: builder.query<Product[], void>({ query: () => 'products', keepUnusedDataFor: 10 // 对应10秒缓存 }), }), }) export const { useGetProductsQuery } = productsApi
步骤3:全局注入Redux Provider
新建Providers.tsx:
'use client' import { Provider } from 'react-redux' import { store } from './store' export function ReduxProviders({ children }: { children: React.ReactNode }) { return <Provider store={store}>{children}</Provider> }
修改layout.tsx(App Router):
import { ReduxProviders } from './Providers' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="zh-CN"> <body> <ReduxProviders>{children}</ReduxProviders> </body> </html> ) }
步骤4:修改组件
Page组件可提前预加载数据同步到store(可选):
import CompA from "./CompA"; import { productsApi } from './productsApi' import { store } from './store' const apicall = async () => { const res = await fetch("https://fakestoreapi.com/products", { next:{ revalidate: 10 } }); return await res.json() } const Page = async () => { const data = await apicall() store.dispatch(productsApi.util.setQueryData('getProducts', data)) return <CompA /> } export default Page
CompB直接通过hook获取数据:
'use client' import React from 'react' import Image from 'next/image'; import { Product } from './product.types'; import { FaStar } from "react-icons/fa"; import { useGetProductsQuery } from './productsApi' const CompB = () => { const { data: products, isLoading } = useGetProductsQuery() if (isLoading) return <div>加载中...</div> return ( <div className="products-grid"> {products?.map(product => ( <div key={product.id} className="product-card"> <h3>{product.title}</h3> <Image src={product.image} alt={product.title} width={200} height={200} /> <div className="rating"> {Array(Math.floor(product.rating.rate)).fill(0).map((_, i) => ( <FaStar key={i} /> ))} <span>{product.rating.count}条评价</span> </div> </div> ))} </div> ); }; export default CompB
CompA同样无需传递props,直接渲染CompB即可。
方案三:Async Server Component(最简洁)
如果CompB不需要客户端交互,直接改为Server Component,在组件内部直接调用API,完全避免props传递。
修改CompB组件(去掉'use client'):
import React from 'react' import Image from 'next/image'; import { Product } from './product.types'; import { FaStar } from "react-icons/fa"; const apicall = async () => { const res = await fetch("https://fakestoreapi.com/products", { next:{ revalidate: 10 } }); return await res.json() } const CompB = async () => { const products = await apicall() return ( <div className="products-grid"> {products.map(product => ( <div key={product.id} className="product-card"> <h3>{product.title}</h3> <Image src={product.image} alt={product.title} width={200} height={200} /> <div className="rating"> {Array(Math.floor(product.rating.rate)).fill(0).map((_, i) => ( <FaStar key={i} /> ))} <span>{product.rating.count}条评价</span> </div> </div> ))} </div> ); }; export default CompB
Page和CompA都简化为:
// Page组件 import CompA from "./CompA"; const Page = () => { return <CompA /> } export default Page // CompA组件 import React from 'react' import CompB from './CompB' const CompA = () => { return <CompB /> } export default CompA
内容的提问来源于stack exchange,提问作者MD SHEHAB
相关产品推荐
相关产品推荐

