React 组件通信完整指南 以及 自定义事件发布订阅系统

React 组件通信完整指南

1. 父子组件通信

1.1 父组件向子组件传递数据

// 父组件
function ParentComponent() {const [data, setData] = useState('Hello from parent');return <ChildComponent message={data} />;
}// 子组件
function ChildComponent({ message }) {return <div>{message}</div>;
}

1.2 子组件向父组件传递数据

// 父组件
function ParentComponent() {const handleChildData = (data) => {console.log('Received from child:', data);};return <ChildComponent onDataSend={handleChildData} />;
}// 子组件
function ChildComponent({ onDataSend }) {const sendData = () => {onDataSend('Hello from child');};return <button onClick={sendData}>Send Data to Parent</button>;
}

1.3 父组件调用子组件方法

// 父组件
function ParentComponent() {const childRef = useRef();const handleClick = () => {childRef.current.childMethod();};return (<div><ChildComponent ref={childRef} /><button onClick={handleClick}>Call Child Method</button></div>);
}// 子组件
const ChildComponent = forwardRef((props, ref) => {useImperativeHandle(ref, () => ({childMethod: () => {console.log('Child method called');}}));return <div>Child Component</div>;
});

2. 兄弟组件通信

2.1 通过共同父组件

function ParentComponent() {const [sharedData, setSharedData] = useState('');return (<div><SiblingOne onDataChange={setSharedData} /><SiblingTwo data={sharedData} /></div>);
}function SiblingOne({ onDataChange }) {return (<button onClick={() => onDataChange('Hello from Sibling One')}>Send to Sibling</button>);
}function SiblingTwo({ data }) {return <div>Received: {data}</div>;
}

2.2 使用 Context

// 创建 Context
const DataContext = React.createContext();// 父组件提供 Context
function ParentComponent() {const [sharedData, setSharedData] = useState('');return (<DataContext.Provider value={{ data: sharedData, setData: setSharedData }}><SiblingOne /><SiblingTwo /></DataContext.Provider>);
}// 兄弟组件一
function SiblingOne() {const { setData } = useContext(DataContext);return (<button onClick={() => setData('Hello from Context')}>Update Context</button>);
}// 兄弟组件二
function SiblingTwo() {const { data } = useContext(DataContext);return <div>Context Data: {data}</div>;
}

3. 消息订阅与发布

3.1 使用 PubSubJS

PubSubJS 是一个基于主题的发布/订阅库。

  • 官方文档: https://github.com/mroderick/PubSubJS
  • 安装: npm install pubsub-js
  • 接受消息的组件订阅消息
  • 提供数据的组件发布消息
  • 可在兄弟组件,祖孙组件进行通讯
基本用法示例
import PubSub from 'pubsub-js';// 定义消息主题
const TOPICS = {USER_LOGGED_IN: 'USER_LOGGED_IN',DATA_UPDATED: 'DATA_UPDATED',NOTIFICATION: 'NOTIFICATION'
};// 登录组件(发布者)
function LoginComponent() {const handleLogin = () => {// 登录成功后发布消息PubSub.publish(TOPICS.USER_LOGGED_IN, {userId: '123',username: 'john_doe',timestamp: new Date()});};return <button onClick={handleLogin}>Login</button>;
}// 头部组件(订阅者)
function HeaderComponent() {const [username, setUsername] = useState('');useEffect(() => {// 订阅登录消息const token = PubSub.subscribe(TOPICS.USER_LOGGED_IN, (topic, data) => {setUsername(data.username);console.log(`User ${data.username} logged in at ${data.timestamp}`);});return () => {// 组件卸载时取消订阅PubSub.unsubscribe(token);};}, []);return <div>Welcome, {username}</div>;
}// 通知组件(订阅者)
function NotificationComponent() {const [notifications, setNotifications] = useState([]);useEffect(() => {// 可以同时订阅多个主题const tokens = [PubSub.subscribe(TOPICS.USER_LOGGED_IN, (topic, data) => {setNotifications(prev => [...prev, `New login: ${data.username}`]);}),PubSub.subscribe(TOPICS.DATA_UPDATED, (topic, data) => {setNotifications(prev => [...prev, `Data updated: ${data.message}`]);})];return () => {// 清理所有订阅tokens.forEach(token => PubSub.unsubscribe(token));};}, []);return (<div><h3>Notifications</h3><ul>{notifications.map((note, index) => (<li key={index}>{note}</li>))}</ul></div>);
}

3.2 自定义事件发布订阅系统

// eventBus.js
class EventBus {constructor() {this.events = {};}subscribe(event, callback) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(callback);return () => {this.events[event] = this.events[event].filter(cb => cb !== callback);};}publish(event, data) {if (this.events[event]) {this.events[event].forEach(callback => callback(data));}}
}export default new EventBus();

3.2.1. EventBus 类

class EventBus {constructor() {this.events = {};}

EventBus 是一个类,里面有一个 events 对象,用来存储所有事件及其对应的回调函数。
this.events 以事件名为键 (key),回调函数数组为值 (value),用来存储订阅的事件和回调函数。

3.2.2. subscribe 方法

subscribe(event, callback) {if (!this.events[event]) {this.events[event] = [];}this.events[event].push(callback);return () => {this.events[event] = this.events[event].filter(cb => cb !== callback);};
}

subscribe 方法用于订阅某个事件 (event) 并提供一个回调函数 (callback)。
如果事件名 event 不存在于 this.events 中,会初始化为一个空数组。
然后把回调函数添加到事件对应的回调函数数组中。
subscribe 方法返回一个取消订阅的函数。这是通过在返回值中使用 filter 方法,从 this.events[event] 数组中移除给定的回调函数来实现的。
订阅示例:


const unsubscribe = eventBus.subscribe('someEvent', (data) => {console.log(data);
});

这样,当 ‘someEvent’ 事件发生时,回调会执行。
调用 unsubscribe() 可以取消订阅该事件的回调。

3.2.3. publish 方法

publish(event, data) {if (this.events[event]) {this.events[event].forEach(callback => callback(data));}
}

publish 方法用于触发某个事件 (event),并向订阅该事件的回调函数传递数据 (data)。
如果事件在 this.events 中存在,它会依次执行所有与该事件相关的回调函数,并把 data 作为参数传递给回调函数。
发布事件示例:

eventBus.publish('someEvent', { key: 'value' });

这会触发所有订阅 ‘someEvent’ 的回调,并将 { key: ‘value’ } 传递给它们。

3.2.4. 实例化 EventBus

export default new EventBus();

这一行创建了一个 EventBus 的实例,并将其导出。这样其他模块就可以直接使用这个实例来订阅和发布事件,而无需每次都创建新的 EventBus 实例。

3.2.5 总结

订阅事件:通过 subscribe 方法,可以为某个事件注册一个回调函数。
发布事件:通过 publish 方法,可以触发某个事件,并将数据传递给所有已订阅该事件的回调函数。
取消订阅:subscribe 返回的函数可以用来取消订阅某个事件。

// 使用自定义事件系统
import eventBus from './eventBus';// 发布者组件
function Publisher() {const publishEvent = () => {eventBus.publish('customEvent', {message: 'Hello from custom event'});};return <button onClick={publishEvent}>Publish Event</button>;
}// 订阅者组件
function Subscriber() {const [message, setMessage] = useState('');useEffect(() => {const unsubscribe = eventBus.subscribe('customEvent', (data) => {setMessage(data.message);});return () => unsubscribe();}, []);return <div>Custom Event Message: {message}</div>;
}

3.3 使用 RxJS

// 安装: npm install rxjsimport { Subject } from 'rxjs';const messageSubject = new Subject();// 发布者组件
function RxPublisher() {const publishMessage = () => {messageSubject.next({text: 'Hello from RxJS',timestamp: new Date()});};return <button onClick={publishMessage}>Publish RxJS Message</button>;
}// 订阅者组件
function RxSubscriber() {const [message, setMessage] = useState('');useEffect(() => {const subscription = messageSubject.subscribe(data => {setMessage(data.text);});return () => subscription.unsubscribe();}, []);return <div>RxJS Message: {message}</div>;
}

4. 最佳实践

4.1 选择合适的通信方式

  1. 父子组件通信

    • 优先使用 props 和回调函数
    • 需要调用子组件方法时使用 ref
  2. 兄弟组件通信

    • 简单场景:通过共同父组件
    • 复杂场景:使用 Context 或状态管理库
  3. 跨层级组件通信

    • 使用 Context
    • 使用消息订阅发布
    • 考虑使用状态管理库(Redux/MobX)

4.2 性能优化

// 使用 useMemo 优化 props
function ParentComponent() {const [count, setCount] = useState(0);const expensiveData = useMemo(() => {return computeExpensiveValue(count);}, [count]);return <ChildComponent data={expensiveData} />;
}// 使用 useCallback 优化回调函数
function ParentComponent() {const handleClick = useCallback((value) => {console.log(value);}, []);return <ChildComponent onClick={handleClick} />;
}

4.3 注意事项

  1. 清理订阅
useEffect(() => {const subscription = someEventSource.subscribe();return () => subscription.unsubscribe();
}, []);
  1. 避免过度使用全局状态
// 不推荐
const GlobalContext = React.createContext();// 推荐:将 Context 拆分为更小的粒度
const UserContext = React.createContext();
const ThemeContext = React.createContext();
  1. 合理使用 memo
const MemoizedChild = React.memo(ChildComponent, (prevProps, nextProps) => {return prevProps.value === nextProps.value;
});

5. 总结

组件通信方式选择建议:

  1. 就近原则

    • 父子组件优先使用 props
    • 兄弟组件优先通过父组件通信
  2. 灵活性考虑

    • 简单场景使用 props 和回调
    • 复杂场景考虑发布订阅或状态管理
  3. 性能考虑

    • 合理使用 useMemo 和 useCallback
    • 适当使用 React.memo
    • 注意清理订阅避免内存泄漏
  4. 维护性考虑

    • 保持通信逻辑清晰
    • 避免过度使用全局状态
    • 合理划分组件职责

实际应用场景

  1. 跨组件通信:
// 数据更新组件(发布者)
function DataUpdateComponent() {const updateData = () => {// 执行数据更新操作PubSub.publish(TOPICS.DATA_UPDATED, {message: 'Data has been updated',timestamp: new Date()});};return <button onClick={updateData}>Update Data</button>;
}// 多个需要响应数据更新的组件(订阅者)
function TableComponent() {const [data, setData] = useState([]);useEffect(() => {const token = PubSub.subscribe(TOPICS.DATA_UPDATED, () => {// 重新获取数据fetchData().then(setData);});return () => PubSub.unsubscribe(token);}, []);return <table>{/* 渲染数据 */}</table>;
}function ChartComponent() {const [chartData, setChartData] = useState(null);useEffect(() => {const token = PubSub.subscribe(TOPICS.DATA_UPDATED, () => {// 更新图表数据updateChartData();});return () => PubSub.unsubscribe(token);}, []);return <div>{/* 渲染图表 */}</div>;
}
  1. 全局状态变化通知:
// 主题切换组件(发布者)
function ThemeToggle() {const toggleTheme = () => {const newTheme = 'dark';PubSub.publish('THEME_CHANGED', { theme: newTheme });};return <button onClick={toggleTheme}>Toggle Theme</button>;
}// 需要响应主题变化的组件(订阅者)
function ThemedComponent() {const [theme, setTheme] = useState('light');useEffect(() => {const token = PubSub.subscribe('THEME_CHANGED', (_, data) => {setTheme(data.theme);// 更新组件样式});return () => PubSub.unsubscribe(token);}, []);return <div className={theme}>{/* 组件内容 */}</div>;
}

3.2 使用注意事项

  1. 命名约定:
// 使用常量定义主题名称
const TOPICS = {USER_ACTION: 'USER_ACTION',SYSTEM_EVENT: 'SYSTEM_EVENT',DATA_CHANGE: 'DATA_CHANGE'
};// 使用命名空间避免冲突
const TOPICS = {USER: {LOGIN: 'USER.LOGIN',LOGOUT: 'USER.LOGOUT'},DATA: {UPDATE: 'DATA.UPDATE',DELETE: 'DATA.DELETE'}
};
  1. 性能考虑:
function OptimizedComponent() {useEffect(() => {// 使用防抖或节流处理高频事件const handleDataChange = debounce((topic, data) => {// 处理数据变化}, 200);const token = PubSub.subscribe('DATA_CHANGE', handleDataChange);return () => PubSub.unsubscribe(token);}, []);
}
  1. 错误处理:
function RobustSubscriber() {useEffect(() => {const token = PubSub.subscribe('TOPIC', (topic, data) => {try {// 处理数据} catch (error) {console.error('Error handling published data:', error);// 错误处理逻辑}});return () => PubSub.unsubscribe(token);}, []);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/65487.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac中idea菜单工具栏没有git图标了

1.右击菜单工具栏 2.选中VCS&#xff0c;点击添加 3.搜索你要的工具&#xff0c;选中点击确定就添加了 4.回到上面一个界面&#xff0c;选中你要放到工具栏的工具&#xff0c;点击应用就好了 5.修改图标&#xff0c;快捷键或者右击选中编辑图标 6.选择你要的图标就好了

Linux实验报告7-文件管理

目录 一&#xff1a;实验目的 二&#xff1a;实验内容 (1)查看/etc/inittab文件的权限属性&#xff0c;并指出该文件的所有者以及文件所属组群。 (2)新建文件test&#xff0c;设置文件权限为r--r-----。 (3)新建文件test2&#xff0c;设系统中有用户study和用户组studygr…

CPT203 Software Engineering 软件工程 Pt.4 软件设计(中英双语)

文章目录 6. 设计概念6.1 Principle6.2 Concepts6.2.1 General design concepts&#xff08;常见的设计概念&#xff09;6.2.1.1 Abstraction&#xff08;抽象&#xff09;6.2.1.2 Modularity&#xff08;模块化&#xff09;6.2.1.3 Functional independence&#xff08;功能独…

JSON 系列之4:JSON_VALUE

JSON_VALUE的作用&#xff0c;简单来说&#xff0c;就是从JSON到SQL&#xff1a; SQL/JSON function JSON_VALUE selects JSON data and returns a SQL scalar or an instance of a user-defined SQL object type or SQL collection type (varray, nested table) 所以&#xff…

[wzoi]Help Bubu

题目描述: Bubu的书架上乱成一团了&#xff01;请帮助他一下吧&#xff01; 他的书架上一共有n本书。我们定义混乱值是连续相同高度书本的段数。例如&#xff0c;如果输的高度是30,30,31,31,32&#xff0c;那么混乱值为3,30,32,32,31的混乱度也是3&#xff0c;但31,32,31,32,…

嵌入式AI STM32部署卷积神经网络的魔法棒

基于STM32部署卷积神经网络控制设备方案-AI项目-STM32部署卷积神经网络方案-红外信号复制方案-轨迹识别 项目包含下述内容 硬件部分、PCB制板、BOM表文件等等 (Hardware)外壳、3D打印文件 (3D_print)软件程序、用于电子法棒的软件程序 AI Keil等等(Software)QT上位机动作识别…

【代码分析】Unet-Pytorch

1&#xff1a;unet_parts.py 主要包含&#xff1a; 【1】double conv&#xff0c;双层卷积 【2】down&#xff0c;下采样 【3】up&#xff0c;上采样 【4】out conv&#xff0c;输出卷积 """ Parts of the U-Net model """import torch im…

[Leetcode] 最大子数组和 [击败99%的解法]

解法1&#xff1a; 暴力解法 遍历每个元素&#xff0c;从它当前位置一直加到最后&#xff0c;然后用一个最大值来记录全局最大值。 代码如下&#xff1a; class Solution {public int maxSubArray(int[] nums) {long sum, max nums[len-1];for (int i0; i<nums.length;…

系统压力测试助手——stress-ng

1、背景 在系统性能测试和压力测试中&#xff0c;stress-ng 是一个非常强大的工具&#xff0c;广泛应用于对 Linux 系统进行各种硬件和软件方面的负载测试。它能够模拟多种极端负载情况&#xff0c;帮助开发人员和运维人员检查系统在高负载下的表现&#xff0c;以便发现潜在的…

计算机网络500题2024-2025学年度第一学期复习题库(选择、判断、填空)

一、单选题 1、&#xff08; &#xff09;是实现两个同种网络互连的设备 A. 网桥 B. 网关 C. 集线器 D. 路由器 2、10M以太网有三种接口标准&#xff0c;其中10BASE-T采用&#xff08; &#xff09; A. 双绞线 B. 粗同轴电缆 C. 细同轴电缆 D. 光纤 3、HDLC是哪…

在JavaScript文件中定义方法和数据(不是在对象里定以数据和方法,不要搞错了)

在对象里定以数据和方法看这一篇 对象字面量内定义属性和方法&#xff08;什么使用const等关键字&#xff0c;什么时候用键值对&#xff09;-CSDN博客https://blog.csdn.net/m0_62961212/article/details/144788665 下是在JavaScript文件中定义方法和数据的基本方式&#xff…

基于SpringBoot的垃圾分类系统设计与实现【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的垃圾分类系统设计了三种角色、分别是管理员、垃圾分类管理员、用户&#xff0c;实现了个人中心、用户管理、垃圾分类管理员管理、垃圾分类管理、垃圾类型管理、垃圾图谱管理、系统管理等功能 技术选型 开发工具&#xff1a;idea2020.3Webst…

今日总结 2024-12-28

今天全身心投入到鸿蒙系统下 TCPSocket 的学习中。从最基础的 TCP 协议三次握手、四次挥手原理重新梳理&#xff0c;深刻理解其可靠连接建立与断开机制&#xff0c;这是后续运用 TCPSocket 无误通信的根基。在深入鸿蒙体系时&#xff0c;仔细研读了其为 TCPSocket 封装的 API&a…

springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失

这个包丢失了 启动不了 起因是pom中加入了 <tomcat.version></tomcat.version>版本指定&#xff0c;然后idea自动编译后&#xff0c;包丢了&#xff0c;删除这个配置后再也找不回来&#xff0c; 这个包正常在 <dependency><groupId>org.springframe…

前后端分离(对话框的使用)

1.首先先定义两个按钮(一个添加按钮&#xff0c;一个修改按钮) <el-button type"primary" click"openDialog(true)">添加员工</el-button> <el-button size"mini" click"openDialog(false, scope.row)">编辑</…

doris集群存储目录切换

doris集群存储目录切换 1. 背景 3节点集群&#xff0c;BE存储目录&#xff0c;因为运维原因。存储盘系统放在了一一起。 需要增加硬盘&#xff0c;并替换原有目录。 3节点集群&#xff0c;如果各个表都是3副本&#xff0c;可以实现轮流停机&#xff0c;方式处理。 但是业务…

【Maven_bugs】The project main artifact does not exist

背景&#xff1a;我想使用 maven-shade-plugin 打一个 fat jar 时报了标题中的错误&#xff0c;使用的命令是&#xff1a;org.apache.maven.plugins:maven-shade-plugin:shade -pl :shade-project。项目结构如下图&#xff0c;我想把子模块 shade-project 打成一个 fat jar&…

Qt 的信号槽机制详解:之信号槽引发的 Segmentation Fault 问题拆析(上)

Qt 的信号槽机制详解&#xff1a;之因信号槽误用引发的 Segmentation Fault 问题拆析&#xff08;上&#xff09; 前言一. 信号与槽的基本概念信号&#xff08;Signal&#xff09;槽&#xff08;Slot&#xff09;连接信号与槽 二. 信号槽机制的实现原理元对象系统&#xff08;M…

贪心算法(常见贪心模型)

常见贪心模型 简单排序模型 最小化战斗力差距 题目分析&#xff1a; #include <bits/stdc.h> using namespace std;const int N 1e5 10;int n; int a[N];int main() {// 请在此输入您的代码cin >> n;for (int i 1;i < n;i) cin >> a[i];sort(a1,a1n);…

Docker 安装与配置 Nginx

摘要 1、本文全面介绍了如何在 Docker 环境中安装和配置 Nginx 容器。 2、文中详细解释了如何设置 HTTPS 安全连接及配置 Nginx 以实现前后端分离的代理服务。 2、同时&#xff0c;探讨了通过 IP 和域名两种方式访问 Nginx 服务的具体配置方法 3、此外&#xff0c;文章还涵…