React18+快速入门 - 3.组件通讯[完整版]

我将详细讲解 React 中各种组件通信方式,从基础到高级,并附上实际示例。

一、父子组件通信

1. Props 向下传递(父 → 子)

最基本的方式,父组件通过 props 向子组件传递数据。

jsx 复制代码
// 父组件
import React from 'react';
import ChildComponent from './ChildComponent';

function ParentComponent() {
  const message = "Hello from Parent";
  const user = { name: "John", age: 30 };
  
  return (
    <div>
      <h1>父组件</h1>
      <ChildComponent 
        message={message} 
        user={user}
        count={5}
      />
    </div>
  );
}

// 子组件
function ChildComponent(props) {
  return (
    <div>
      <h2>子组件</h2>
      <p>消息: {props.message}</p>
      <p>用户名: {props.user.name}</p>
      <p>计数: {props.count}</p>
    </div>
  );
}

// 或者使用解构
function ChildComponent({ message, user, count }) {
  return (
    <div>
      <h2>子组件</h2>
      <p>消息: {message}</p>
      <p>用户名: {user.name}</p>
      <p>计数: {count}</p>
    </div>
  );
}

2. 回调函数向上传递(子 → 父)

子组件通过调用父组件传递的回调函数,向父组件传递数据。

jsx 复制代码
// 父组件
function ParentComponent() {
  const [count, setCount] = useState(0);
  
  // 处理子组件的数据
  const handleChildData = (dataFromChild) => {
    console.log("从子组件接收的数据:", dataFromChild);
    setCount(dataFromChild);
  };
  
  const handleMessage = (message) => {
    alert(`子组件说: ${message}`);
  };
  
  return (
    <div>
      <h1>父组件 - 计数: {count}</h1>
      <ChildComponent 
        onDataSend={handleChildData}
        onMessageSend={handleMessage}
      />
    </div>
  );
}

// 子组件
function ChildComponent({ onDataSend, onMessageSend }) {
  const [inputValue, setInputValue] = useState('');
  
  const handleClick = () => {
    // 调用父组件传递的函数
    onDataSend(10);
  };
  
  const handleSendMessage = () => {
    onMessageSend(inputValue);
    setInputValue('');
  };
  
  return (
    <div>
      <h2>子组件</h2>
      <button onClick={handleClick}>发送数据到父组件</button>
      <div>
        <input 
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="输入消息"
        />
        <button onClick={handleSendMessage}>发送消息</button>
      </div>
    </div>
  );
}

二、兄弟组件通信

3. 状态提升(通过共同的父组件)

将共享状态提升到最近的共同父组件中。

jsx 复制代码
// 父组件
function ParentComponent() {
  const [sharedData, setSharedData] = useState('初始数据');
  
  return (
    <div>
      <h1>父组件</h1>
      <ChildA 
        data={sharedData}
        onDataChange={setSharedData}
      />
      <ChildB data={sharedData} />
    </div>
  );
}

// 子组件A
function ChildA({ data, onDataChange }) {
  return (
    <div style={{ border: '1px solid blue', padding: '10px', margin: '10px' }}>
      <h3>子组件A</h3>
      <p>当前数据: {data}</p>
      <button onClick={() => onDataChange('A组件修改的数据')}>
        修改数据
      </button>
    </div>
  );
}

// 子组件B
function ChildB({ data }) {
  return (
    <div style={{ border: '1px solid green', padding: '10px', margin: '10px' }}>
      <h3>子组件B</h3>
      <p>从A组件接收的数据: {data}</p>
    </div>
  );
}

三、跨层级组件通信

4. Context API(官方解决方案)

适用于跨多层组件传递数据,避免"prop drilling"(属性层层传递)。

jsx 复制代码
// 1. 创建Context
import React, { createContext, useState, useContext } from 'react';

// 创建Context对象
const UserContext = createContext();
const ThemeContext = createContext();

// 2. 创建Provider组件
function App() {
  const [user, setUser] = useState({ name: '张三', age: 25 });
  const [theme, setTheme] = useState('light');
  
  const updateUser = (newUser) => {
    setUser({ ...user, ...newUser });
  };
  
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };
  
  return (
    // 提供多个Context
    <UserContext.Provider value={{ user, updateUser }}>
      <ThemeContext.Provider value={{ theme, toggleTheme }}>
        <div style={{ padding: '20px' }}>
          <h1>主应用</h1>
          <ComponentA />
          <ComponentC />
        </div>
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

// 3. 中间层组件(不需要传递props)
function ComponentA() {
  return (
    <div style={{ border: '1px solid gray', padding: '10px', margin: '10px' }}>
      <h2>组件A</h2>
      <ComponentB />
    </div>
  );
}

// 4. 消费Context的组件
function ComponentB() {
  // 使用useContext Hook消费Context
  const { user, updateUser } = useContext(UserContext);
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  const style = {
    backgroundColor: theme === 'light' ? '#f5f5f5' : '#333',
    color: theme === 'light' ? '#000' : '#fff',
    padding: '10px',
    margin: '10px'
  };
  
  return (
    <div style={style}>
      <h3>组件B(使用Context)</h3>
      <p>用户名: {user.name}</p>
      <p>年龄: {user.age}</p>
      <button onClick={() => updateUser({ age: user.age + 1 })}>
        增加年龄
      </button>
      <button onClick={toggleTheme}>
        切换主题 (当前: {theme})
      </button>
    </div>
  );
}

// 5. 类组件消费Context(使用Consumer)
class ComponentC extends React.Component {
  render() {
    return (
      <UserContext.Consumer>
        {({ user }) => (
          <ThemeContext.Consumer>
            {({ theme }) => (
              <div style={{
                backgroundColor: theme === 'light' ? '#e8f5e9' : '#1b5e20',
                padding: '10px',
                margin: '10px'
              }}>
                <h3>组件C(类组件使用Consumer)</h3>
                <p>用户名: {user.name}</p>
              </div>
            )}
          </ThemeContext.Consumer>
        )}
      </UserContext.Consumer>
    );
  }
}

四、高级通信方式

5. 使用Ref(父组件直接调用子组件方法)

jsx 复制代码
// 子组件(使用forwardRef和useImperativeHandle)
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';

// 子组件
const ChildComponent = forwardRef((props, ref) => {
  const [count, setCount] = useState(0);
  
  // 暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    // 子组件的方法
    increment: () => {
      setCount(count + 1);
    },
    decrement: () => {
      setCount(count - 1);
    },
    reset: () => {
      setCount(0);
    },
    // 子组件的数据
    getCount: () => {
      return count;
    }
  }));
  
  return (
    <div style={{ border: '1px solid red', padding: '10px', margin: '10px' }}>
      <h3>子组件</h3>
      <p>内部计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>内部+1</button>
    </div>
  );
});

// 父组件
function ParentComponent() {
  const childRef = useRef(null);
  
  const handleIncrement = () => {
    childRef.current?.increment();
  };
  
  const handleDecrement = () => {
    childRef.current?.decrement();
  };
  
  const handleReset = () => {
    childRef.current?.reset();
  };
  
  const getChildCount = () => {
    const count = childRef.current?.getCount();
    alert(`子组件的计数: ${count}`);
  };
  
  return (
    <div>
      <h1>父组件</h1>
      <button onClick={handleIncrement}>调用子组件increment()</button>
      <button onClick={handleDecrement}>调用子组件decrement()</button>
      <button onClick={handleReset}>调用子组件reset()</button>
      <button onClick={getChildCount}>获取子组件计数</button>
      
      <ChildComponent ref={childRef} />
    </div>
  );
}

6. 事件总线/发布订阅模式

适用于非父子组件、任意组件间的通信。

jsx 复制代码
// eventBus.js - 事件总线
class EventBus {
  constructor() {
    this.events = {};
  }
  
  // 订阅事件
  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }
  
  // 发布事件
  emit(eventName, data) {
    if (this.events[eventName]) {
      this.events[eventName].forEach(callback => {
        callback(data);
      });
    }
  }
  
  // 取消订阅
  off(eventName, callback) {
    if (this.events[eventName]) {
      this.events[eventName] = this.events[eventName].filter(
        cb => cb !== callback
      );
    }
  }
}

// 创建全局事件总线实例
export const eventBus = new EventBus();

// Component1.jsx - 发布者组件
import React, { useEffect, useState } from 'react';
import { eventBus } from './eventBus';

function Component1() {
  const [message, setMessage] = useState('');
  
  const sendMessage = () => {
    // 发布事件
    eventBus.emit('messageEvent', {
      text: message,
      timestamp: new Date().toLocaleTimeString()
    });
    setMessage('');
  };
  
  return (
    <div style={{ border: '1px solid blue', padding: '10px', margin: '10px' }}>
      <h3>组件1 (发布者)</h3>
      <input
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        placeholder="输入消息"
      />
      <button onClick={sendMessage}>发送消息</button>
      
      <button onClick={() => eventBus.emit('globalEvent', '全局事件触发')}>
        触发全局事件
      </button>
    </div>
  );
}

// Component2.jsx - 订阅者组件
function Component2() {
  const [receivedMessages, setReceivedMessages] = useState([]);
  
  useEffect(() => {
    // 定义事件处理函数
    const handleMessage = (data) => {
      setReceivedMessages(prev => [...prev, data]);
    };
    
    const handleGlobalEvent = (data) => {
      console.log('收到全局事件:', data);
    };
    
    // 订阅事件
    eventBus.on('messageEvent', handleMessage);
    eventBus.on('globalEvent', handleGlobalEvent);
    
    // 组件卸载时取消订阅
    return () => {
      eventBus.off('messageEvent', handleMessage);
      eventBus.off('globalEvent', handleGlobalEvent);
    };
  }, []);
  
  return (
    <div style={{ border: '1px solid green', padding: '10px', margin: '10px' }}>
      <h3>组件2 (订阅者)</h3>
      <h4>收到的消息:</h4>
      <ul>
        {receivedMessages.map((msg, index) => (
          <li key={index}>
            {msg.text} - {msg.timestamp}
          </li>
        ))}
      </ul>
    </div>
  );
}

// App.jsx
function App() {
  return (
    <div>
      <h1>事件总线示例</h1>
      <Component1 />
      <Component2 />
      <Component3 />
    </div>
  );
}

// Component3.jsx - 另一个订阅者
function Component3() {
  const [lastMessage, setLastMessage] = useState('');
  
  useEffect(() => {
    const handleMessage = (data) => {
      setLastMessage(`最新消息: ${data.text} (${data.timestamp})`);
    };
    
    eventBus.on('messageEvent', handleMessage);
    
    return () => {
      eventBus.off('messageEvent', handleMessage);
    };
  }, []);
  
  return (
    <div style={{ border: '1px solid orange', padding: '10px', margin: '10px' }}>
      <h3>组件3 (另一个订阅者)</h3>
      <p>{lastMessage}</p>
    </div>
  );
}

7. 状态管理库(Redux/MobX/Zustand)

当应用状态复杂时,推荐使用状态管理库。这里以Zustand为例(最简单):

jsx 复制代码
// store.js - 创建store
import create from 'zustand';

const useStore = create((set, get) => ({
  // 状态
  user: null,
  todos: [],
  theme: 'light',
  
  // Actions - 更新状态的方法
  setUser: (user) => set({ user }),
  
  addTodo: (todo) => set((state) => ({
    todos: [...state.todos, todo]
  })),
  
  removeTodo: (id) => set((state) => ({
    todos: state.todos.filter(todo => todo.id !== id)
  })),
  
  toggleTheme: () => set((state) => ({
    theme: state.theme === 'light' ? 'dark' : 'light'
  })),
  
  // 计算属性
  get completedTodos() {
    return get().todos.filter(todo => todo.completed);
  },
  
  // 异步action
  fetchUser: async (userId) => {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    set({ user });
  }
}));

// ComponentA.jsx - 使用store
function ComponentA() {
  // 选择需要的state和actions
  const { user, theme, setUser, toggleTheme } = useStore();
  
  return (
    <div style={{ 
      backgroundColor: theme === 'light' ? '#fff' : '#333',
      color: theme === 'light' ? '#000' : '#fff',
      padding: '20px',
      margin: '10px'
    }}>
      <h3>组件A</h3>
      <p>当前用户: {user ? user.name : '未登录'}</p>
      <p>当前主题: {theme}</p>
      
      <button onClick={() => setUser({ name: '李四', id: 1 })}>
        设置用户
      </button>
      
      <button onClick={toggleTheme}>
        切换主题
      </button>
    </div>
  );
}

// ComponentB.jsx - 使用store的另一部分
function ComponentB() {
  const { todos, addTodo, removeTodo, completedTodos } = useStore();
  
  const [newTodo, setNewTodo] = useState('');
  
  const handleAddTodo = () => {
    if (newTodo.trim()) {
      addTodo({
        id: Date.now(),
        text: newTodo,
        completed: false
      });
      setNewTodo('');
    }
  };
  
  return (
    <div style={{ border: '1px solid #ccc', padding: '20px', margin: '10px' }}>
      <h3>组件B - Todo列表</h3>
      
      <div>
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="输入todo"
        />
        <button onClick={handleAddTodo}>添加</button>
      </div>
      
      <h4>所有Todo ({todos.length})</h4>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            {todo.text}
            <button onClick={() => removeTodo(todo.id)} style={{ marginLeft: '10px' }}>
              删除
            </button>
          </li>
        ))}
      </ul>
      
      <h4>已完成 ({completedTodos.length})</h4>
      <ul>
        {completedTodos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

// App.jsx
function App() {
  return (
    <div>
      <h1>Zustand状态管理示例</h1>
      <ComponentA />
      <ComponentB />
    </div>
  );
}

五、通信方式对比与选择指南

通信方式 适用场景 优点 缺点
Props传递 父子组件简单通信 简单直观,React核心机制 多层传递麻烦(prop drilling)
回调函数 子向父传递数据 简单直接 多层传递复杂
状态提升 兄弟组件通信 逻辑集中,易于理解 状态提升可能使父组件臃肿
Context API 跨多层组件,主题/用户等全局数据 官方方案,避免prop drilling 不适合频繁更新的数据
Ref 父组件需要调用子组件方法 直接访问子组件 破坏React数据流,谨慎使用
事件总线 任意组件间通信,解耦组件 完全解耦,灵活 难以调试,可能产生内存泄漏
状态管理库 复杂应用状态管理 专业状态管理,可预测性 增加复杂度,学习成本

六、最佳实践建议

  1. 简单场景优先使用Props和回调函数
  2. 主题/用户等全局数据使用Context
  3. 复杂业务状态使用Redux/Zustand等状态库
  4. 尽量避免使用事件总线,除非必要
  5. 谨慎使用Ref直接操作子组件

总结

React 组件通信有多种方式,选择哪种取决于你的具体需求:

  1. 简单父子通信 → Props + 回调函数
  2. 兄弟组件通信 → 状态提升
  3. 跨层级组件 → Context API
  4. 复杂应用状态 → 状态管理库(Redux/Zustand)
  5. 特殊场景 → Ref 或事件总线

记住:保持数据流单向、可预测是 React 的核心原则。选择合适的通信方式可以让你的应用更易于维护和调试。

相关推荐
一起养小猫13 小时前
Flutter实战:从零实现俄罗斯方块(三)交互控制与事件处理
javascript·flutter·交互
lethelyh13 小时前
Vue day1
前端·javascript·vue.js
酉鬼女又兒13 小时前
SQL113+114 更新记录(一)(二)+更新数据知识总结
java·服务器·前端
无风听海13 小时前
AngularJS中 then catch finally 的语义、执行规则与推荐写法
前端·javascript·angular.js
利刃大大13 小时前
【Vue】组件化 && 组件的注册 && App.vue
前端·javascript·vue.js
Whisper_Sy13 小时前
Flutter for OpenHarmony移动数据使用监管助手App实战 - 周报告实现
开发语言·javascript·网络·flutter·php
Anastasiozzzz13 小时前
leetcodehot100--最小栈 MinStack
java·javascript·算法
一起养小猫14 小时前
Flutter for OpenHarmony 实战:按钮类 Widget 完全指南
前端·javascript·flutter
css趣多多14 小时前
Vux store实例的模块化管理
前端
我是伪码农15 小时前
Vue 1.26
前端·javascript·vue.js