Vue中的发布订阅模式分析

模块:instanceEventEmiiter.ts(在下方有简单实现和解析)

在Vue3中,已经取消了对这个模块的引用,故而不再支持 $on、$off、$once相关的方法,不过还是可以对进行学习和借鉴,运用到工作中。

  1. Vue3中的简单实现

    Vue3中 emit 的实现相对 Vue2 来说更加简单一些了,他是通过 h函数 的第二个参数来实现的

    实现 Child 组件

    const { createApp, h } = Vue
    // 创建一个子组件
    const Child = {
    setup(props, ctx) {
    return {
    buttonClick() {
    // 源码中挂载 emit 的函数:createComponentInstance
    // 挂载代码:instance.emit = emit.bind(null, instance)
    // emit函数接受3个参数: emit(instance, event, ...rawArgs)
    // 其中 instance 在挂载到 instance 的时候默认传入了
    // ctx.emit('test') 相当于 ctx.emit('当前Vue实例', 'test', 123)
    // emit函数中会对 on开头、 Once 结尾等关键字符串进行解析
    // 然后从 instance.vnode.props 中去获取对应的回调函数
    // 然后执行 回调函数,此次发布订阅流程也就完成了 // 执行 emit 方法发布一个 test 事件
    ctx.emit('test', 123)
    }
    }
    },
    render(e) {
    return h('button', {
    onClick: this.buttonClick
    }, '派发Emit')
    }
    }

    实现App组件

    const App = {
    render(e) {
    // h函数返回一个 vnode
    // 当 h 函数的第二个参数是一个 Object,并且不是一个 VNode 时
    // vnode.props 就是 h 函数的第二个参数值
    return h(
    'div', {}, [
    // 引用 Child 组件,并且传入 onTest 方法
    h(Child, {
    // 监听 test 事件 vnode.props = { onText: Funtion }
    onTest(e) {
    console.log('emit test event!', e)
    }
    })
    ]
    )
    }
    } createApp(App).mount(document.getElementById('emitApp'))
  2. instanceEventEmiiter 实现代码(简易 JS 版本)

    · 其中像 WeakMap对象、getRegistry方法的运用,及对与 eventName 为数组的处理逻辑还是值得学习的

    · 在工作中可以在 Vue3 的项目中实现一个 Emiiter 模块,代替 Vue2中的 bus 实现,用于全局事件通讯

    // 发布订阅仓库
    // 数据结构类似: { Instance, Object<[event: string]: Function[] | undefined> }
    const eventRegistryMap = new WeakMap() /**
    * 根据 instance 进行事件注册
    * @param {*} instance 当前 Vue 实例对象
    * @returns 当前事件对象s
    */
    function getRegistry(instance) {
    // 从 map 中获取事件对象
    let events = eventRegistryMap.get(instance) if(!events) {
    // 若没有注册过,则进行新注册
    eventRegistryMap.set(instance, (events = Object.create(null)))
    } // 返回 实例的事件对象
    return events
    } /**
    * 添加订阅
    * @param {*} instance 实例
    * @param {*} eventName 事件名称 string | string[]
    * @param {*} fn 回调函数 function
    */
    function on(instance, eventName, fn) {
    if(Array.isArray(eventName)) {
    // 处理 eventName 为 数组的情况
    //(如:this.$on(['tab:click', 'tab:change'], () => {}))
    eventName.forEach(c => on(instance, c, fn))
    } else{
    // 获取 instance实例 的事件对象的
    let events = getRegistry(instance)
    // 将回调函数添加到事件对象的 回调函数 列表中
    (events[eventName] || (events[eventName] = [])).push(fn)
    }
    } /**
    * 发布订阅
    * @param {*} instance 实例
    * @param {*} eventName 事件名称 string | string[]
    * @param {*} args 回调函数 function
    */
    function emit(instance, eventName, args) {
    // 获取 instance实例 中事件名称为 eventName 的回调函数列表
    const cbs = getRegistry(instance)[eventName] if(cbs) {
    // 在 Vue 源码中,这里是通过 callWithAsyncErrorHandling 函数统一进行执行的
    // 目的是为了统一收集回调函数中的异常 // 执行回调函数(这里我们直接调用了)
    cbs.forEach(fn => {
    fn.apply(instance.proxy, args)
    })
    }
    } /**
    * 添加订阅(只触发一次)
    * @param {*} instance 实例
    * @param {*} eventName 事件名称 string | string[]
    * @param {*} fn 回调函数 function
    * @returns
    */
    function once(instance, eventName, fn) {
    // 由于只需要执行一次,这里对 回调 函数进行了包装
    const wrapped = (...args) => {
    // 执行回调是我们需要吧他删除掉
    off(instance, eventName, fn)
    // 执行回调函数
    fn.call(instance.proxy, ...args)
    } // 在包装函数上增加一个 fn属性,用于删除这个回调函数时做匹配
    wrapped.fn = fn
    // 添加到 订阅 列表中
    on(instance, eventName, wrapped)
    return instance.proxy
    } /**
    * 移除订阅事件
    * @param {*} instance 实例
    * @param {*} eventName 事件名称 string | string[]
    * @param {*} fn 回调函数 function
    * @returns
    */
    function off(instance, eventName, fn) {
    if(Array.isArray(eventName)) {
    // 处理 eventName 为 数组的情况
    eventName.forEach(c => off(instance, c, fn))
    } else {
    const vm = instance.proxy
    const events = getRegistry(instance)
    const cbs = events[eventName]
    // 事件 订阅列表 为空时提前处理
    if(!cbs) {
    return vm
    } // 没有传入回调函数,直接将 事件订阅列表 清空即可
    if(!fn) {
    events[eventName] = undefined
    return vm
    } // 过滤掉需要清除的回调,重新赋值给这个事件的 订阅列表
    events[eventName] = cbs.filter(c => !(c === fn))
    }
    }

成功之前我们要做应该做的事情,成功之后我们才可以做喜欢做的事情

Vue中的发布订阅分析(Vue2/3中的 emit 实现)的更多相关文章

  1. JS中的发布订阅模式

    一. 你是如何理解发布订阅模式的 JS中的设计模式: 单例模式:处理业务逻辑 构造原型模式:封装类库,组件,框架,插件等 类库:jQuery 只是提供了一些常用的方法,可以应用到任何的项目中,不具备业 ...

  2. Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 如何实现发布--订阅模式? 发布---订阅模式的代码封装 如何取消订阅事件? 全局--发布订阅对象代码封装 理解模块间通信 回到 ...

  3. [转] Javascript中理解发布--订阅模式

    发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 现实生活中的发布- ...

  4. redis中的发布订阅(Pub/Sub)

    这里使用nodejs的redis模块说明,具体可见https://www.npmjs.com/package/redis,先来通过一个简单的例子了解下redis中的Pub/Sub具体怎么实现吧.. v ...

  5. 【转】Javascript中理解发布--订阅模式

    Javascript中理解发布--订阅模式 阅读目录 发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时 ...

  6. javascript中的发布订阅模式与观察者模式

    这里了解一下JavaScript中的发布订阅模式和观察者模式,观察者模式是24种基础设计模式之一. 设计模式的背景 设计模式并非是软件开发的专业术语,实际上设计模式最早诞生于建筑学. 设计模式的定义是 ...

  7. python开发-实现redis中的发布订阅功能

    Python3学习(二十七):python实现Redis的订阅与发布(sub-pub机制) 先介绍一下redis的pub/sub功能: Pub/Sub功能(means Publish, Subscri ...

  8. RedisRepository封装—Redis发布订阅以及StackExchange.Redis中的使用

    本文版权归博客园和作者本人吴双共同所有,转载请注明本Redis系列分享地址.http://www.cnblogs.com/tdws/tag/NoSql/ Redis Pub/Sub模式 基本介绍 Re ...

  9. OrcharNoCMS中的发布订阅使用

    对于Orchard里面的EventBus,没有太多的文章去介绍说明.它最好的应用是发布订阅的应用. 使用介绍: 在Car模块中,我们定义一个接口,继承IEventHandler接口. 当我们在创建一条 ...

随机推荐

  1. 【剑指Offer】09. 用两个栈实现队列 解题报告(python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 个人微信公众号:负雪明烛 目录 题目描述 解题方法 一个栈用来保存输入,一个栈用来输出 日 ...

  2. 初识TMMi——测试成熟度模型集成

    利用零碎的时间,粗略了解了一下TMMi V1.2,整理一下学习笔记跟大家分享一下. 本文分为四个部分,分别为TMMi概述.TMMi结构.成熟度级别和过程域.TMMi实施周期,希望能够帮助大家更好的理解 ...

  3. Python调用Prometheus监控数据并计算

    Prometheus是什么 Prometheus是一套开源监控系统和告警为一体,由go语言(golang)开发,是监控+报警+时间序列数 据库的组合.适合监控docker容器.因为kubernetes ...

  4. Bristol大学密码学博士生的五十二个知识点

    Bristol大学密码学博士生的五十二个知识点 这个系列,是Bristol大学的密码安全工作组为密码学和信息安全相关的博士准备了52个基本知识点. 原地址:http://bristolcrypto.b ...

  5. 使用Xcode 制作自定义storyboard启动界面,供uniAPP使用。

    1新建项目 想要全屏显示并适应所有尺寸的iPad和iphone 需要用750*1624 2X 和 1125 * 2436 3X大小的图片 这里做完就可以导出文件了 把文件和图片放到一起 见下图 命名规 ...

  6. RTD2171U替代方案|CS5266Typec转HDMI 4K60HZ转换芯片方案|CS5266芯片

    RTD2171U 是一款USB Type-C到HDMI转换器结合了USB Type-C输入接口和数字高清多媒体接口(HDMI)输出转换芯片.嵌入式微控制器基于工业标准8051内核.接收器端口将信道配置 ...

  7. CS5210完全替代AG6202|HDMI转VGA不带音频输出的芯片+原理图|替代兼容AG6202

    CS5210完全替代AG6202|HDMI转VGA不带音频输出的芯片+原理图|替代兼容AG6202 安格AG6202是一个HDMI转VGA不带音频解决方案,用于实现HDMI1.4高分辨率视频转VGA转 ...

  8. <数据结构>关键路径

    目录 AOV网和AOE网 AOV网 AOE网 定义 与AOV网的转化 AOE网中着重解决的两个问题 1.最长路径问题 2.关键活动问题 总结 最长路径 无正环的图 有向无环图的最短路径 其他情况 关键 ...

  9. 在 jQuery 中使用滑入滑出动画效果,实现二级下拉导航菜单的显示与隐藏效果

    查看本章节 查看作业目录 需求说明: 在 jQuery 中使用滑入滑出动画效果,实现二级下拉导航菜单的显示与隐藏效果 用户将光标移动到"最新动态页"或"帮助查询" ...

  10. JZOJ5966. [NOIP2018TGD2T3] 保卫王国 (动态DP做法)

    题目大意 这还不是人尽皆知? 有一棵树, 每个节点放军队的代价是\(a_i\), 一条边连接的两个点至少有一个要放军队, 还有\(q\)次询问, 每次规定其中的两个一定需要/不可放置军队, 问这样修改 ...