第13章 组件通信
父子组件、兄弟组件之间的数据传递
作者:zyyc-0336 | 日期:2026-06-12
🚀 本章是 React 阶段的最后一章,学完后进入 Next.js 阶段。
📋 学习路径建议
- 先学 13.1(父子通信),掌握 Props 和回调的基本模式
- 再学 13.2(状态提升),理解为什么要"提升"状态
- 最后学 13.3(Context API),解决"层层传递 Props"的问题
⚠️ 常见错误提示
⚠️ 子组件不能直接修改父组件的 state——必须通过父组件传下来的回调函数
⚠️ Context 适合"全局共享"的数据(主题、用户信息),不适合频繁变化的数据
⚠️ Context 的 Provider 要放在使用数据的组件的外层
⚠️
useContext只能读取最近的 Provider 的值
13.1 父子通信
父子通信的核心就两条路:父 → 子(通过 Props),子 → 父(通过回调函数)。
父 → 子:父亲给儿子东西(Props 传数据)
子 → 父:儿子通知父亲事情(调用父亲给的回调函数)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>父子通信示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// ========== 子组件:文章卡片 ==========
// 接收 Props(父 → 子)
// 接收回调(子 → 父)
function PostCard({ title, author, likes, onLike, onDelete }) {
return (
<div style={{
border: '1px solid #ddd',
borderRadius: '8px',
padding: '16px',
marginBottom: '12px'
}}>
<h3 style={{ margin: '0 0 8px 0' }}>{title}</h3>
<p style={{ color: '#666', margin: '0 0 12px 0' }}>作者:{author}</p>
<div style={{ display: 'flex', gap: '8px' }}>
{/* 子组件通过调用回调通知父组件 */}
<button onClick={onLike}>❤️ {likes}</button>
<button onClick={onDelete} style={{
backgroundColor: '#e74c3c',
color: 'white',
border: 'none',
padding: '4px 12px',
borderRadius: '4px',
cursor: 'pointer'
}}>
删除
</button>
</div>
</div>
);
}
// ========== 父组件:文章列表 ==========
function App() {
let [posts, setPosts] = React.useState([
{ id: 1, title: 'JavaScript 入门', author: '小明', likes: 5 },
{ id: 2, title: 'CSS 布局技巧', author: '小红', likes: 3 },
{ id: 3, title: 'React 快速上手', author: '小明', likes: 8 }
]);
// 父组件定义处理函数,通过 Props 传给子组件
function handleLike(id) {
setPosts(posts.map(post =>
post.id === id ? { ...post, likes: post.likes + 1 } : post
));
}
function handleDelete(id) {
setPosts(posts.filter(post => post.id !== id));
}
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<h1>文章列表 ({posts.length})</h1>
{posts.map(post => (
<PostCard
key={post.id}
title={post.title}
author={post.author}
likes={post.likes}
onLike={() => handleLike(post.id)}
onDelete={() => handleDelete(post.id)}
/>
))}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
数据流向图
父组件 App
├── state: posts(数据在父组件)
├── 通过 Props 传数据给子组件 ↓
│ title, author, likes
└── 通过回调接收子组件通知 ↑
onLike, onDelete
子组件 PostCard
├── 接收 Props 显示内容
└── 用户操作时调用回调通知父组件
💡 核心原则:数据永远单向流动——从父到子。子组件想改数据,就"告诉"父组件去改。
13.2 状态提升
状态提升:当两个子组件需要共享同一份数据时,把 state 提升到它们共同的父组件中。
就像两个孩子都需要零花钱——钱放在爸妈那里,孩子需要时跟爸妈要。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>状态提升示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// 温度输入组件
function TemperatureInput({ label, temperature, onTemperatureChange }) {
return (
<div style={{ marginBottom: '10px' }}>
<label>{label}:</label>
<input
type="number"
value={temperature}
onChange={(e) => onTemperatureChange(Number(e.target.value))}
style={{ padding: '6px', marginLeft: '8px', width: '80px' }}
/>
</div>
);
}
// 状态提升到父组件——两个输入框共享同一个温度数据
function TemperatureConverter() {
let [celsius, setCelsius] = React.useState(0);
function handleCelsiusChange(value) {
setCelsius(value);
}
function handleFahrenheitChange(value) {
setCelsius((value - 32) * 5 / 9);
}
let fahrenheit = celsius * 9 / 5 + 32;
return (
<div style={{ maxWidth: '400px', margin: '0 auto', padding: '20px' }}>
<h1>温度转换器(状态提升)</h1>
<TemperatureInput
label="摄氏度 (°C)"
temperature={Math.round(celsius * 100) / 100}
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
label="华氏度 (°F)"
temperature={Math.round(fahrenheit * 100) / 100}
onTemperatureChange={handleFahrenheitChange}
/>
<hr />
<p>转换结果:{Math.round(celsius * 100) / 100}°C = {Math.round(fahrenheit * 100) / 100}°F</p>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<TemperatureConverter />);
</script>
</body>
</html>
状态提升的场景
| 场景 | 说明 |
|---|---|
| 两个输入框联动 | 温度转换器、汇率计算器 |
| 兄弟组件共享数据 | 文章列表和文章详情都用同一份文章数据 |
| 子组件影响兄弟 | 选中一个列表项,详情面板显示对应内容 |
13.3 Context API
Context API 是 React 提供的"全局数据共享"方案。当数据需要跨越很多层组件传递时,不用一层一层传 Props,直接通过 Context "广播"。
为什么需要 Context?
想象你的博客有一个"主题"功能(亮色/暗色),几乎每个组件都要知道当前主题。用 Props 传的话:
App → Layout → Header → NavBar → ThemeButton (每层都要传 theme)
用 Context:
App (Provider) → ... → ThemeButton (useContext) (直接获取,不用层层传)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Context API 示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// ========== 步骤1:创建 Context ==========
let ThemeContext = React.createContext('light');
// ========== 步骤4:任何子组件都可以直接读取 ==========
function ThemeButton() {
let theme = React.useContext(ThemeContext);
let style = {
padding: '10px 20px',
fontSize: '16px',
cursor: 'pointer',
border: 'none',
borderRadius: '6px',
backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
color: theme === 'dark' ? '#fff' : '#333'
};
return <button style={style}>当前主题:{theme === 'dark' ? '🌙 暗色' : '☀️ 亮色'}</button>;
}
function PostCard() {
let theme = React.useContext(ThemeContext);
let cardStyle = {
padding: '16px',
marginTop: '16px',
borderRadius: '8px',
backgroundColor: theme === 'dark' ? '#1a1a2e' : '#ffffff',
color: theme === 'dark' ? '#e0e0e0' : '#333333',
border: theme === 'dark' ? '1px solid #333' : '1px solid #ddd'
};
return (
<div style={cardStyle}>
<h3>JavaScript 入门指南</h3>
<p>这篇文章会根据主题自动变色...</p>
</div>
);
}
// ========== 步骤2:用 Provider 包裹子组件 ==========
function App() {
let [theme, setTheme] = React.useState('light');
function toggleTheme() {
setTheme(theme === 'light' ? 'dark' : 'light');
}
return (
<ThemeContext.Provider value={theme}>
<div style={{
maxWidth: '600px',
margin: '0 auto',
padding: '20px',
backgroundColor: theme === 'dark' ? '#0f0f23' : '#f5f5f5',
minHeight: '100vh',
transition: 'all 0.3s'
}}>
<h1 style={{ color: theme === 'dark' ? '#fff' : '#333' }}>
主题切换示例
</h1>
<button onClick={toggleTheme} style={{
padding: '8px 20px',
marginBottom: '20px',
cursor: 'pointer'
}}>
切换到{theme === 'light' ? '暗色' : '亮色'}主题
</button>
<ThemeButton />
<PostCard />
</div>
</ThemeContext.Provider>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Context 使用四步走
| 步骤 | 代码 | 说明 |
|---|---|---|
| 1. 创建 | let Ctx = React.createContext(默认值) | 创建 Context |
| 2. 提供 | <Ctx.Provider value={值}> | 用 Provider 包裹,传入数据 |
| 3. 消费 | let value = React.useContext(Ctx) | 子组件用 useContext 读取 |
| 4. 更新 | 改变 Provider 的 value | 所有消费组件自动更新 |
⚠️ 新手易错:Context 的
value变了,所有用useContext的组件都会重新渲染。如果 Context 里放了频繁变化的大对象,可能有性能问题。⚠️ 新手易错:没有被
Provider包裹的组件用useContext,会拿到创建 Context 时的默认值。确保Provider包在最外层。
本章复盘——组件通信核心要点
| 要点 | 说明 |
|---|---|
| 父 → 子 | 通过 Props 传递数据 |
| 子 → 父 | 通过回调函数(父组件定义函数传给子组件) |
| 状态提升 | 兄弟组件共享数据时,把 state 提到共同父组件 |
| Context | 创建 → Provider 包裹 → useContext 消费 |
| Context 场景 | 主题、用户信息、语言等"全局"数据 |
| Props 场景 | 父子直接通信、组件复用时传不同数据 |
下一章预告(第13章):我们将学习State 与事件处理——useState Hook、事件绑定和表单处理。
本文档由 zyyc-0336 编写,最后更新:2026-06-14