第13章 组件通信

父子组件、兄弟组件之间的数据传递

作者:zyyc-0336  |  日期:2026-06-12

🚀 本章是 React 阶段的最后一章,学完后进入 Next.js 阶段。
📋 学习路径建议
  1. 先学 13.1(父子通信),掌握 Props 和回调的基本模式
  2. 再学 13.2(状态提升),理解为什么要"提升"状态
  3. 最后学 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