当前位置: 首页 > 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条点击跳转-点击历史搜索记录可同步到搜索框并自动搜索全部删除-可一次性全部删除历史搜索记录全部删除-有再次确认操作展示 进行搜索后留下搜索记录 点击垃圾桶图标,显示【清空全部】 点击【清…...

OpenLayers 可视化之热力图

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 热力图&#xff08;Heatmap&#xff09;又叫热点图&#xff0c;是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...

解析“道作为序位生成器”的核心原理

解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制&#xff0c;重点解析"道作为序位生成器"的核心原理与实现框架&#xff1a; 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...