发布订阅模式的应用:解决react中复杂层级的数据交互

以自定义事件为调度中心,创建一个EventCenter类,默认导出一个EventCenter实例

复制代码
// 调度中心
class EventCenter {
    constructor() {
        // 创建一个事件中心,数据模型:{ event : [fn, fn] }
        this.eventCenter = {};
    }

    /**
     * 订阅事件
     * eventName {string} 订阅事件名称
     * backFn {Function} 收到通知的回调函数
     **/
    listen(eventName, backFn) {
        // 确定是否有订阅过该事件
        if (!this.eventCenter[eventName]) {
            this.eventCenter[eventName] = [];
        }
        // 控制backFn的唯一性,防止出现重复的backFn
        const backFnList = this.eventCenter[eventName];
        const hasFn = backFnList.some((fn) => fn === backFn);
        if (hasFn) {
            return;
        }
        backFnList.push(backFn);
    }
    
    /**
     * 取消订阅函数
     * eventName {string} 取消订阅的事件名称
     * backFn {Function} 取消订阅的回调函数
     **/
    unListen(eventName, backFn) {
        // 判断是否有该事件,及该事件是否有订阅者
        let backFnList = this.eventCenter[eventName];
        if (!backFnList || backFnList.length === 0) {
            return;
        }
        backFnList = backFnList.filter((fn) => fn !== backFn); // 过滤掉取消订阅的函数
    }
    
    /**
     * 发布函数
     * eventName {string} 发布通知的事件名称
     * ...args {any} 发布通知的相关参数
     **/
    publish(eventName, ...args) {
        // 判断是否有该事件,及该事件是否有订阅者
        const backFnList = this.eventCenter[eventName];
        if (!backFnList || backFnList.length === 0) {
            return;
        }
        backFnList.forEach((backFn) => {
            backFn.apply(null, args); // 调用回调函数,并传入发布时的相关参数
        });
    }
}

export default new EventCenter(); // 默认导出一个实例

publish中使用了apply是为了把参数进行转换,具体请参考此文章:《javascript 关于bind、apply、call函数改变this指向》
运用场景一: 添加商品与购物车的数量变化 ,这种情况往往是不知道跨越多少不同层级的情况。
发布者 :商品加入购物车的操作
订阅者:购物车的数量

复制代码
// 商品列表 ProductList.js
import EventCenter from './EventCenter';
const ProductList = ()=>{
	// 模拟商品数据
	const productList = [
		{id:1, name: '商品1'}
	];
	const addBuyCar = (product)=>{
		EventCenter.publish('addBuyCar', product); // 发布通知,添加商品到购物车
	}
	return (
		<ul>
			{productList.map(product => {
				return (<li key={product.id}>
					商品名称:{product.name} 
					<button onClick={()=>addBuyCar(product)}>加入购物车</button>
				</li>)
			})}
		</ul>
	);
}
export default ProductList;

// 购物车商品种类数量 BuyCarCount.js
import {useEffect, useState} from 'react';
import EventCenter from './EventCenter';
const BuyCarCount = ()=>{

	const [productList, setProductList] = useState([]); // 产品种类列表
	
	useEffect(()=>{
		// 订阅添加商品时的回调函数
		const backFn = (product)=>{
			setProductList(pList => {
				// 判断购物车是否已存在此商品,没有才添加进去
				const hasProduct = pList.some(pro => pro.id === product.id);
				if(!hasProduct){
					return [...pList, product];
				}
				return pList;
			});
		}
		
		EventCenter.listen('addBuyCar', backFn); // 订阅addBuyCar事件
		return ()=> EventCenter.unListen('addBuyCar', backFn); // 组件销毁后,取消订阅
	}, []);
	
	return <div>购物车商品种类:{productList.map(item=> item.name)}</div>;
}
export default BuyCarCount;
相关推荐
skiyee2 分钟前
🚀 用 17 行代码给 UniApp 加上全局登录拦截
前端·uni-app
默_笙31 分钟前
🌃 HTTP 不认识你:JWT 登录鉴权的完整"酒店入住"指南
前端·javascript
Profile排查笔记1 小时前
JavaScript 实现浏览器指纹生成:基础字段、Canvas 采样与 SHA-256 摘要
前端·人工智能·后端·自动化
不可能片场1 小时前
Electron 子进程退化成 node 的 env 陷阱
前端·electron
搬砖记录员1 小时前
录屏总黑屏?90% 的人没关这个开关——浏览器硬件加速冲突排查指南
前端·音视频开发
promiseThen1 小时前
我用 7 条铁律管住 Cursor:让 AI 写代码不再「自由发挥」
前端·ai编程
半个落月1 小时前
React JWT 登录鉴权实战:Mock、Zustand、Axios 与路由守卫全流程
后端·react.js
梨想橙汁1 小时前
JS 核心语法:运算符流程控制、函数、数组与对象实战详解
前端·javascript
七牛开发者1 小时前
拆解 dsh:Turn 与 Step 如何组织 Agent 主循环
前端·javascript·人工智能
梨想橙汁1 小时前
JavaScript 零基础入门:引入方式、变量与数据类型,吃透原始与引用类型
前端·javascript