父子组件通信是 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>
);
}
这里的 onIncrement 和 onDecrement 是回调函数 Props。子组件只负责触发,具体的状态更新逻辑完全封装在父组件内部。这符合“单一数据源”的思想。
回调函数的设计惯例
- 命名规范:通常以
on前缀命名,如onChange、onSubmit、onClose,让调用方一目了然这是一个事件回调。 - 参数传递:子组件可以将局部数据作为参数传给回调,比如输入框的当前值:
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 的核心手段。当遇到复杂通信需求时,优先思考能否通过状态提升与回调的组合解决,往往是最简单可靠的方案。