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

如何在父级Server Component下实现React Client组件的状态同步?

问题场景

在我的Next.js应用中,有一个包含两个组件A和B的页面,代码如下:

page.tsx(Server Component)

import A from './A'
import B from './B'

const Page = () => {
  return (
    <div>
      <A />
      <B />
    </div>
  )
}

export default Page

A.tsx(Client Component)

'use client';

import { useState } from 'react'

const A = () => {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  )
}

export default A

B.tsx(Client Component)

'use client';

import { useState } from 'react'

const B = () => {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>click me</button>
    </div>
  )
}

export default B

需求:希望在服务器上静态渲染Page(保持Page为Server Component),同时实现A和B组件的状态同步。


解决方案

由于Page是Server Component,无法直接持有或管理客户端状态,必须在客户端层级实现状态共享,以下是两种可靠的方案:

方案1:状态提升到客户端父组件

创建一个客户端容器组件作为A和B的父组件,将共享状态放在这个容器内,通过props将状态和更新方法传递给A、B。此方案简单直接,适合小型场景。

实现步骤:

  1. 创建客户端容器组件CounterContainer.tsx:
'use client';

import { useState } from 'react';
import A from './A';
import B from './B';

export default function CounterContainer() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(prev => prev + 1);
  };

  return (
    <div>
      <A count={count} incrementCount={incrementCount} />
      <B count={count} incrementCount={incrementCount} />
    </div>
  );
}
  1. 修改A.tsx和B.tsx,移除内部状态,改用props接收数据:
// A.tsx(修改后)
'use client';

type Props = {
  count: number;
  incrementCount: () => void;
};

const A = ({ count, incrementCount }: Props) => {
  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={incrementCount}>click me</button>
    </div>
  );
};

export default A;
// B.tsx(修改后)
'use client';

type Props = {
  count: number;
  incrementCount: () => void;
};

const B = ({ count, incrementCount }: Props) => {
  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={incrementCount}>click me</button>
    </div>
  );
};

export default B;
  1. 更新page.tsx,渲染客户端容器:
// page.tsx(修改后)
import CounterContainer from './CounterContainer';

const Page = () => {
  return <CounterContainer />;
};

export default Page;

方案2:使用React Context API

如果后续需要在更多客户端组件中共享该状态,Context API可以避免多层props传递的麻烦,扩展性更强。

实现步骤:

  1. 创建Context和Provider组件CounterContext.tsx:
'use client';

import { createContext, useContext, useState, ReactNode } from 'react';

type CounterContextType = {
  count: number;
  incrementCount: () => void;
};

const CounterContext = createContext<CounterContextType | undefined>(undefined);

export function CounterProvider({ children }: { children: ReactNode }) {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(prev => prev + 1);
  };

  return (
    <CounterContext.Provider value={{ count, incrementCount }}>
      {children}
    </CounterContext.Provider>
  );
}

export function useCounter() {
  const context = useContext(CounterContext);
  if (!context) {
    throw new Error('useCounter must be used within a CounterProvider');
  }
  return context;
}
  1. 修改A.tsx和B.tsx,使用自定义hooks获取共享状态:
// A.tsx(修改后)
'use client';

import { useCounter } from './CounterContext';

const A = () => {
  const { count, incrementCount } = useCounter();

  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={incrementCount}>click me</button>
    </div>
  );
};

export default A;
// B.tsx(修改后)
'use client';

import { useCounter } from './CounterContext';

const B = () => {
  const { count, incrementCount } = useCounter();

  return (
    <div>
      <p>you clicked {count} times</p>
      <button onClick={incrementCount}>click me</button>
    </div>
  );
};

export default B;
  1. 更新page.tsx,用CounterProvider包裹A和B:
// page.tsx(修改后)
import { CounterProvider } from './CounterContext';
import A from './A';
import B from './B';

const Page = () => {
  return (
    <CounterProvider>
      <A />
      <B />
    </CounterProvider>
  );
};

export default Page;

关键注意点

  • 两种方案均保持Page的Server Component身份,静态渲染不受影响,所有状态管理逻辑完全在客户端层级完成。
  • 禁止在Server Component中直接管理客户端状态,这违反Next.js的组件模型规范,会导致运行错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:20:35