当前位置: 首页 > news >正文

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);}, []);
}

相关文章:

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

React 组件通信完整指南 1. 父子组件通信 1.1 父组件向子组件传递数据 // 父组件 function ParentComponent() {const [data, setData] useState(Hello from parent);return <ChildComponent message{data} />; }// 子组件 function ChildComponent({ message }) {re…...

华为 AI Agent:企业内部管理的智能变革引擎(11/30)

一、华为 AI Agent 引领企业管理新潮流 在当今数字化飞速发展的时代&#xff0c;企业内部管理的高效性与智能化成为了决定企业竞争力的关键因素。华为&#xff0c;作为全球领先的科技巨头&#xff0c;其 AI Agent 技术在企业内部管理中的应用正掀起一场全新的变革浪潮。 AI Ag…...

【Pandas】pandas Series empty

Pandas2.2 Series Attributes 方法描述Series.index每个数据点的标签或索引Series.array对象底层的数据数组Series.values以NumPy数组的形式访问Series中的数据值Series.dtype用于获取 Pandas Series 中数据的类型&#xff08;dtype&#xff09;Series.shape用于获取 Pandas …...

Git如何设置和修改当前分支跟踪的上游分支

目录 前言 背景 设置当前分支跟踪的上游分支 当前分支已有关联&#xff0c;删除其关联&#xff0c;重新设置上游 常用的分支操作 参考资料 前言 仅做学习记录&#xff0c;侵删 背景 在项目开发过程中&#xff0c;从master新建分支时&#xff0c;会出现没有追踪的上游分…...

GitHub新手用法详解【适合新手入门-建议收藏!!!】

目录 什么是Github&#xff0c;为什么使用它&#xff1f; 一、GitHub账号的注册与登录 二、 gitbash安装详解 1.git bash的下载与安装 2.git常用命令 3. Git 和 GitHub 的绑定 1. 获取SSH keys 2.绑定ssh密钥 三、通过Git将代码提交到GitHub 1.克隆仓库 2.测试提交代码…...

游戏开发线性空间下PS工作流程

前言 使用基于物理的渲染&#xff0c;为了保证光照计算的准确&#xff0c;需要使用线性空间&#xff1b; 使用线性空间会带来一个问题&#xff0c;ui 在游戏引擎中的渲染结果与 PS 中的不一致&#xff1a; PS&#xff08;颜色空间默认是sRGB伽马空间&#xff09;&#xff1a…...

7-10 最长公共子序列

目录 题目描述 输入格式: 输出格式: 输入样例: 输出样例: 解题思路&#xff1a; 详细代码&#xff1a; 题目描述 给出 1~n 的两个排列 P1 和 P2&#xff0c;求它们的最长公共子序列。 n 在 5~1000 之间。 输入格式: 第一行是一个数 n 接下来两行&#xff0c;每行为 n 个数&…...

亚远景-ISO 21434标准下的汽车网络安全:风险评估与管理的关键实践

ISO 21434标准&#xff0c;全称为ISO/SAE 21434 "Road Vehicles - Cybersecurity Engineering"&#xff0c;是国际标准化组织(ISO)发布的针对汽车领域的标准&#xff0c;旨在指导汽车制造商、供应商和相关利益相关方在汽车系统中应用适当的网络安全措施。在ISO 21434…...

C++ 的 source_location

1 __FILE__ 和 __LINE__ ​ 你一定看过这样的代码&#xff1a; printf("Internal error at \"%s\" on line %d.\n", __FILE__, __LINE__); 这行代码的作用就是打印出 printf() 函数调用发生时所在的源代码文件名&#xff08;包含路径&#xff09;和这行代…...

[python SQLAlchemy数据库操作入门]-14.实时数据采集 记录股市动态

哈喽,大家好,我是木头左! 要使用 SQLAlchemy 进行实时数据采集,首先需要搭建相应的开发环境。以下是所需的主要步骤: 安装 Python:确保你的系统上已经安装了 Python,推荐使用 Python 3.x 版本。创建虚拟环境:为了隔离项目依赖,建议为每个项目创建一个虚拟环境。可以使…...

`we_chat_union_id IS NOT NULL` 和 `we_chat_union_id != ‘‘` 这两个条件之间的区别

文章目录 1、什么是空字符串&#xff1f;2、两个引号之间加上空格 好的&#xff0c;我们来详细解释一下 we_chat_union_id IS NOT NULL 和 we_chat_union_id ! 这两个条件之间的区别&#xff0c;以及它们在 SQL 查询中的作用&#xff1a; 1. we_chat_union_id IS NOT NULL 含…...

【和春笋一起学C++】文本输入与读取

前言&#xff1a;前面学习了while语句后&#xff0c;下面用while语句实现一个重要的功能&#xff0c;逐字符的读取键盘输入的字符序列&#xff0c;并输出到显示屏上。 准备知识&#xff1a; C的输入输出包含以下3方面的内容&#xff1a; 对系统指定的标准设备的输入和输出。即…...

D类音频应用EMI管理

1、前言 对于EMI&#xff0c;首先需要理解天线。频率和波长之间的关系&#xff0c;如下图所示。   作为有效天线所需的最短长度是λ/4。在空气中&#xff0c;介电常数是1&#xff0c;但是在FR4或玻璃环氧PCB的情况下&#xff0c;介电常数大约4.8。这种效应会导致信号在FR4材…...

第N8周:使用Word2vec实现文本分类

文章目录 一、数据预处理1.加载数据2.构建词典3.生成数据批次和迭代器 二、模型构建1.搭建模型2.初始化模型3.定义训练与评估函数 三、训练模型1.拆分数据集并运行模型 四、总结 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&a…...

100天精通Python(爬虫篇)——第113天:爬虫基础模块之urllib详细教程大全

文章目录 1. urllib概述2. urllib.request模块 1. urllib.request.urlopen()2. urllib.request.urlretrieve()3. urllib.request.Request()4. urllib.request.install_opener()5. urllib.request.build_opener()6. urllib.request.AbstractBasicAuthHandler7. urllib.request.…...

光谱相机与普通相机的区别

一、成像目的 普通相机&#xff1a;主要目的是记录物体的外观形态&#xff0c;生成人眼可见的、直观的二维图像&#xff0c;重点在于还原物体的形状、颜色和纹理等视觉特征&#xff0c;以供人们进行观赏、记录场景或人物等用途。例如&#xff0c;拍摄旅游风景照片、人物肖像等…...

Mysql数据 新增、修改和删除操作时,这些变化如何被转换为Kafka消息?

Mysql数据 新增、修改和删除操作时,这些变化如何被转换为Kafka消息? 为了在FlinkCDC中配置MySQL同步到Kafka,并采用debezium-json数据格式,我们需要了解当执行新增、修改和删除操作时,这些变化如何被转换为Kafka消息。下面我们将详细介绍这些变化情况,并提供具体的数据样…...

《Python 机器视觉:开启智能视觉新时代》

《Python 机器视觉&#xff1a;开启智能视觉新时代》 一、Python 机器视觉的基石&#xff08;一&#xff09;关键库的强大力量&#xff08;二&#xff09;环境搭建的便捷路径 二、核心功能与奇妙应用&#xff08;一&#xff09;图像的奇幻处理&#xff08;二&#xff09;目标检…...

uniapp实现为微信小程序扫一扫的功能

引言 随着微信小程序的快速发展,越来越多的开发者开始关注和学习微信小程序的开发。其中,微信小程序的扫一扫功能是非常常用且实用的功能之一。通过扫描二维码,用户可以获取到相关的信息或者实现特定的功能。 正文 在过去,开发者需要使用微信开发者工具以及相关的开发文档…...

【微信小程序】4plus|搜索框-历史搜索 | 我的咖啡店-综合实训

升级版1-清空全部的再次确认 实现功能: 历史搜索记录展示-历史搜索记录展示10条点击跳转-点击历史搜索记录可同步到搜索框并自动搜索全部删除-可一次性全部删除历史搜索记录全部删除-有再次确认操作展示 进行搜索后留下搜索记录 点击垃圾桶图标,显示【清空全部】 点击【清…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列&#xff0c;以便知晓哪些列包含有价值的数据&#xff0c;…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...