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

Next.js客户端组件数据获取异常及跨组件传参最佳实践咨询

问题分析与解决方案

核心问题拆解

  1. 无限HTTP请求循环:你在'use client'标记的页面组件顶层直接调用async fetchData(),会导致组件每次渲染都执行请求,而setState又会触发新一轮渲染,形成无限循环;同时客户端组件的fetch即使设置no-store也会重复发起请求,直接压垮后端。
  2. Layout到子组件的数据传递:Next.js App Router中Layout无法直接向Page传递props,需要通过合理的模式实现数据共享,避免重复请求。

一、解决无限请求问题的最佳实践

方案1:将初始数据获取与WebSocket逻辑合并到useEffect

把数据初始化和WebSocket连接都放在useEffect中,确保只在组件挂载或依赖变化时执行:

'use client'
import { useState, useEffect } from 'react'

export default function Main({
  params,
}: {
  params: { sport: string; country: string; competition: string };
}) {
  const [newOdds, setOdds] = useState(null);

  useEffect(() => {
    // 1. 初始数据获取
    const fetchInitialData = async () => {
      const res = await fetch("http://127.0.0.1:8000/api/", { cache: 'no-store' });
      const data = await res.json();
      setOdds(data);
    };

    fetchInitialData();

    // 2. WebSocket实时连接
    const ws = new WebSocket("ws://127.0.0.1:8000/ws/odds/");
    ws.onopen = () => {
      // 传递当前赛事参数,让后端返回对应数据
      ws.send(JSON.stringify({ 
        connection: "start",
        ...params 
      }));
    };
    ws.onmessage = (e) => {
      const message = JSON.parse(e.data);
      setOdds(message);
    };

    // 组件卸载时关闭连接
    return () => ws.close();
  }, [params]); // 切换赛事时重新执行逻辑

  if (!newOdds) return <div>Loading...</div>;

  return (
    // 你的渲染内容
  )
}

方案2:用服务端组件预取数据,客户端组件处理实时更新

将Page改为服务端组件,负责获取初始数据,再传递给嵌套的客户端组件处理WebSocket:

// page.tsx (服务端组件,无需'use client')
import ClientOddsComponent from './ClientOddsComponent';

export default async function Main({
  params,
}: {
  params: { sport: string; country: string; competition: string };
}) {
  // 服务端获取数据,自动复用Layout的缓存(Next.js会缓存相同URL的fetch请求)
  const res = await fetch("http://127.0.0.1:8000/api/", { next: { revalidate: 10 } });
  const initialData = await res.json();

  return <ClientOddsComponent initialData={initialData} params={params} />;
}

客户端组件只处理实时更新:

// ClientOddsComponent.tsx ('use client')
import { useState, useEffect } from 'react';

export default function ClientOddsComponent({ initialData, params }) {
  const [newOdds, setOdds] = useState(initialData);

  useEffect(() => {
    const ws = new WebSocket("ws://127.0.0.1:8000/ws/odds/");
    ws.onopen = () => {
      ws.send(JSON.stringify({ 
        connection: "start",
        ...params 
      }));
    };
    ws.onmessage = (e) => {
      const message = JSON.parse(e.data);
      setOdds(message);
    };

    return () => ws.close();
  }, [params]);

  return (
    // 你的渲染内容
  );
}

二、Layout向子组件传递数据的最佳实践

方案:React Context共享数据

通过Context将Layout获取的数据传递给所有子组件,避免重复请求:

  1. 创建Context文件:
// app/contexts/DataContext.tsx
'use client';
import { createContext, useContext } from 'react';

// 替换为你的实际数据类型
type InitialDataType = {
  sports: Array<any>;
  countries: Array<any>;
  // 其他字段
};

type DataContextType = {
  initialData: InitialDataType;
};

const DataContext = createContext<DataContextType | undefined>(undefined);

export function DataProvider({ children, initialData }: { 
  children: React.ReactNode; 
  initialData: InitialDataType 
}) {
  return (
    <DataContext.Provider value={{ initialData }}>
      {children}
    </DataContext.Provider>
  );
}

export function useData() {
  const context = useContext(DataContext);
  if (!context) {
    throw new Error('useData must be used within a DataProvider');
  }
  return context;
}
  1. 在Layout中包裹Provider:
// layout.tsx
import { DataProvider } from './contexts/DataContext';

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const initialData = await fetch("http://127.0.0.1:8000/api/", { next : { revalidate: 10}});
  const data = await initialData.json();

  return (
    <html>
      <body>
        <div>
          <Sidebar initialData={data} />
        </div>
        <DataProvider initialData={data}>
          {children}
        </DataProvider>
      </body>
    </html>
  );
}
  1. 在Page中使用Context数据:
'use client'
import { useData } from '../../contexts/DataContext';

export default function Main({ params }) {
  const { initialData } = useData();
  const [newOdds, setOdds] = useState(initialData);

  // WebSocket逻辑...

  return (
    // 你的渲染内容
  )
}

总结关键最佳实践

  1. 避免客户端组件顶层await:这是触发无限渲染的核心原因,所有异步操作应放在useEffect或服务端组件中。
  2. 复用服务端缓存:利用Next.js服务端组件的fetch自动缓存能力,减少重复请求。
  3. 合理拆分组件职责:服务端组件负责数据预取,客户端组件专注于实时交互(如WebSocket)。
  4. Context共享全局数据:对于Layout获取的公共数据,用Context传递是最简洁的方式,避免重复请求。

内容的提问来源于stack exchange,提问作者sierra_papa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:44:52