2、组件通信与数据流

3 阅读4分钟

太棒了!👏

我们已经让组件“活”了起来(状态 + 事件),接下来要解决一个关键问题:


—— 让组件“说上话”

在真实项目中,组件不是孤立的。它们需要 传递数据响应动作
掌握组件通信,是构建复杂应用的基石。


一、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 实践 —— 语言切换

  1. 创建 LanguageContext
  2. 提供当前语言(如 'zh''en'
  3. 一个按钮切换语言
  4. 一个 <Greeting /> 组件根据语言显示 “你好” 或 “Hello”

✅ 总结:组件通信方式对比

方式适用场景示例
Props父 → 子传数据、配置
Callback 函数子 → 父按钮点击、表单提交
共同父组件兄弟 → 兄弟列表删除、计数器
Context跨层级 / 全局状态主题、语言、登录态

🎯 下一步预告:
你已经掌握了组件之间的“对话”能力!
接下来我们要学习一个核心 Hook:

➡️ useEffect :处理副作用(数据请求、定时器、DOM 操作)

这是连接 React 与外部世界(API、浏览器)的桥梁。

是否继续?我将带你进入 第四课:useEffect 与副作用管理