You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 20:22:42