人人都会AI编程

9.1 父子组件通信

更新时间:2026-07-10

父子组件通信是 React 中最基础、最核心的通信模式。它的设计直接体现了 React 单向数据流的原则:数据向下流动,事件向上通知。理解并熟练运用这一模式,是构建可维护组件树的基石。

核心机制

1. 父传子:通过 Props 向下传递数据

父组件将自身状态或数据,以 JSX 属性的形式传递给子组件。子组件通过函数参数接收这些 Props,并只读地使用它们。Props 是组件对外的稳定接口,父组件是数据的“所有者”,子组件只是数据的“消费者”。

// 父组件
function Parent() {
  const [user, setUser] = useState({ name: '张三', role: 'admin' });

  return (
    <div>
      <h1>用户信息</h1>
      <UserCard user={user} />
    </div>
  );
}

// 子组件
function UserCard({ user }) {
  return (
    <div>
      <p>姓名:{user.name}</p>
      <p>角色:{user.role}</p>
    </div>
  );
}

在这个例子中,Parent 拥有 user 状态,UserCard 通过 Props 接收并展示。UserCard 不能修改 user 的任何属性,保证了数据流的单向性。

2. 子传父:通过回调函数向上通知

当子组件需要触发父组件的状态变更时,不能直接修改 Props,而是调用父组件通过 Props 传递下来的回调函数。父组件在传递回调时可以绑定自己的状态更新逻辑,子组件调用时只是“告知某事发生了”,由父组件决定如何处理。

// 父组件
function Parent() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(prev => prev + 1);
  const decrement = () => setCount(prev => prev - 1);

  return (
    <div>
      <h2>计数器:{count}</h2>
      <CounterControls onIncrement={increment} onDecrement={decrement} />
    </div>
  );
}

// 子组件
function CounterControls({ onIncrement, onDecrement }) {
  return (
    <div>
      <button onClick={onIncrement}>+1</button>
      <button onClick={onDecrement}>-1</button>
    </div>
  );
}

这里的 onIncrementonDecrement 是回调函数 Props。子组件只负责触发,具体的状态更新逻辑完全封装在父组件内部。这符合“单一数据源”的思想。

回调函数的设计惯例

  • 命名规范:通常以 on 前缀命名,如 onChangeonSubmitonClose,让调用方一目了然这是一个事件回调。
  • 参数传递:子组件可以将局部数据作为参数传给回调,比如输入框的当前值:
function Parent() {
  const [text, setText] = useState('');

  const handleChange = (newValue) => {
    setText(newValue);
  };

  return <TextInput value={text} onChange={handleChange} />;
}

function TextInput({ value, onChange }) {
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => onChange(e.target.value)}
    />
  );
}

子组件将输入框的当前值通过 onChange 抛出,父组件决定如何保存。这种模式是受控组件的标准实现方式。

需要注意的边界与最佳实践

1. 避免过度传递回调

如果组件嵌套层级过深,逐层传递回调会导致中间的组件被迫接收与自己无关的 Props,这种现象称为“Props Drilling”。此时应改用 Context 或状态管理库,而不是通过多层父子传递。

2. 回调函数的引用稳定性

如果父组件每次渲染都会生成一个新的函数(例如非 useCallback 包裹的内联函数),会导致子组件收到一个新的 Props 引用,可能引发不必要的重渲染,即使子组件使用了 React.memo 优化。

// ❌ 每次父组件刷新,increment 都是新函数
<CounterControls onIncrement={() => setCount(prev => prev + 1)} />

// ✅ 使用 useCallback 保持引用稳定
const increment = useCallback(() => setCount(prev => prev + 1), []);
<CounterControls onIncrement={increment} />

对于性能敏感的场景,建议使用 useCallback 包裹传递给子组件的回调。

3. 不滥用“子调父”改变上层状态

尽量让状态的所有者就近管理。如果一个状态只在某个子树内部使用,就不要强行提升到顶层再由回调修改。遵循“状态就近原则”,可以减少不必要的全局耦合。

与其他通信方式的关系

父子通信是所有其他通信方案的基础。兄弟组件通信可以通过将共享状态提升到共同的父组件,结合 Props 和回调来实现;跨层级通信则通过 Context 提供全局访问点,但本质上仍然是 Props 和回调的延伸。掌握父子通信,就掌握了 React 组件通信的根基。

小结

父子通信遵循“Props 向下,回调向上”的单向数据流模式:

  • 父传子:数据通过只读的 Props 传递。
  • 子传父:事件通过回调函数通知,由父组件执行状态变更。

这种模式让数据变化路径清晰可追踪,是构建可预测、可维护 UI 的核心手段。当遇到复杂通信需求时,优先思考能否通过状态提升与回调的组合解决,往往是最简单可靠的方案。