太棒了!👏
我们已经让组件“活”了起来(状态 + 事件),接下来要解决一个关键问题:
—— 让组件“说上话”
在真实项目中,组件不是孤立的。它们需要 传递数据、响应动作。
掌握组件通信,是构建复杂应用的基石。
一、React 数据流原则:单向数据流 🌊
React 的数据流动是 自上而下(从父到子) 的:
父组件 → 子组件 → 孙子组件
- 数据通过 props 向下传递
- 子组件通过 回调函数 向上传递事件
- 不能“跨级直连”,要靠“中间人”传递
✅ 类比:公司层级
老板(父)→ 经理(子)→ 员工(孙)
员工想提建议?→ 找经理 → 经理汇报给老板
二、1. 父传子:通过 Props 传递数据 ✅
这是最基础、最常用的通信方式。
示例:博客文章列表
// 子组件:单篇文章
function Post({ title, content, author }) {
return (
<article className="post">
<h2>{title}</h2>
<p>{content}</p>
<small>作者:{author}</small>
</article>
);
}
// 父组件:文章列表
function Blog() {
const posts = [
{ id: 1, title: 'React 入门', content: 'JSX 是...', author: '小明' },
{ id: 2, title: 'Hooks 进阶', content: 'useState...', author: '小红' },
];
return (
<div>
{posts.map(post => (
<Post
key={post.id}
title={post.title}
content={post.content}
author={post.author}
/>
))}
</div>
);
}
📌 要点:
- 父组件把数据作为
props传给子组件 - 子组件“只读”使用这些数据
三、2. 子传父:通过回调函数(Callback)传递事件 ⬆️
子组件无法直接修改父组件的 state,但可以通过 调用父组件传来的函数 来“通知”父组件。
示例:带删除功能的待办事项
// 子组件:单个任务
function TodoItem({ todo, onDelete }) {
return (
<li>
{todo.text}
<button onClick={() => onDelete(todo.id)}>
删除
</button>
</li>
);
}
// 父组件:任务列表
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React' },
{ id: 2, text: '写项目' },
]);
// 删除逻辑在父组件
const handleDelete = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={handleDelete} // 把函数传下去
/>
))}
</ul>
);
}
📌 关键点:
onDelete是一个函数,由父组件定义,传给子组件- 子组件点击时调用它:
onDelete(todo.id) - 父组件收到通知后更新自己的 state
🔥 这就是 “状态提升”(Lifting State Up) 的典型模式!
四、3. 兄弟组件通信 ↔️
两个组件没有直接父子关系,如何通信?
✅ 解决方案:通过共同父组件中转
示例:计数器 + 显示器(兄弟关系)
// 子组件 A:按钮
function IncrementButton({ onIncrement }) {
return <button onClick={onIncrement}>+1</button>;
}
// 子组件 B:显示器
function Display({ count }) {
return <p>当前数值:{count}</p>;
}
// 父组件:协调两者
function CounterApp() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<IncrementButton onIncrement={handleIncrement} />
<Display count={count} />
</div>
);
}
📌 通信路径:
IncrementButton → (调用 onIncrement) → CounterApp → (传 count) → Display
✅ 所有“兄弟通信”本质都是“子→父→子”的中转。
五、4. 跨多层级通信:Context API 🌐
当组件层级很深时,一层层传递 props 就像“接力赛”,非常繁琐 —— 这叫 prop drilling(属性钻取)
示例:主题切换(深嵌套)
// 1. 创建 Context
const ThemeContext = React.createContext();
// 2. 父组件提供值(Provider)
function App() {
const [isDark, setIsDark] = useState(false);
const toggleTheme = () => {
setIsDark(!isDark);
};
return (
<ThemeContext.Provider value={{ isDark, toggleTheme }}>
<Header />
<Main />
<Footer />
</ThemeContext.Provider>
);
}
// 3. 深层子组件消费值(Consumer 或 useContext)
function Button() {
const { isDark, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{ background: isDark ? '#333' : '#f0f0f0' }}
>
切换主题
</button>
);
}
📌 Context 适用场景:
- 主题、语言、用户登录状态等“全局配置”
- 避免深层传递相同 props
⚠️ 注意:不要滥用 Context,它会让组件复用性降低。
✅ 优先考虑“props 传递”或“状态管理库(如 Zustand)”。
六、实战练习 🏋️♂️
✅ 练习 1:父子通信 —— 搜索框
- 父组件有一个
<SearchResults query={query} /> - 子组件是一个输入框,用户输入时更新父组件的
query SearchResults显示 “正在搜索:xxx”
提示:把 setQuery 函数传给子组件
✅ 练习 2:兄弟通信 —— 计数器控制面板
- 两个按钮组件:
<IncrementBtn />和<DecrementBtn /> - 一个显示组件:
<CounterDisplay /> - 所有功能由父组件统一管理 state
目标:点击按钮能改变显示器的数字
✅ 练习 3:Context 实践 —— 语言切换
- 创建
LanguageContext - 提供当前语言(如
'zh'或'en') - 一个按钮切换语言
- 一个
<Greeting />组件根据语言显示 “你好” 或 “Hello”
✅ 总结:组件通信方式对比
| 方式 | 适用场景 | 示例 |
|---|---|---|
| Props | 父 → 子 | 传数据、配置 |
| Callback 函数 | 子 → 父 | 按钮点击、表单提交 |
| 共同父组件 | 兄弟 → 兄弟 | 列表删除、计数器 |
| Context | 跨层级 / 全局状态 | 主题、语言、登录态 |
🎯 下一步预告:
你已经掌握了组件之间的“对话”能力!
接下来我们要学习一个核心 Hook:
➡️ useEffect :处理副作用(数据请求、定时器、DOM 操作)
这是连接 React 与外部世界(API、浏览器)的桥梁。
是否继续?我将带你进入 第四课:useEffect 与副作用管理。