[HarmonyOS][K老师]关于我自己封装了emitter,我是这么实现的。 原创
头像 K老师 2026-01-08 16:40:47    发布
12940 浏览 336 点赞 0 收藏

提示:家人们如果觉得太多的话可以看前面的思路,然后直接跳到最后面复制代码到dev里面运行即可,代码主要的我都注释了

我在写鸿蒙的一个工具类项目的时候想自己封装一个emitter挑战一下,用来实现是否已学习这个状态的时候,使用

emitter.on 触发事件,emitter.emit 监听事件,emitter是用到里面的设计模式,或者可以叫做发布订阅/自定义事件模式,还有一种模式,策略模式,混合开发同样会有这一套模式,所以他是一套通用的通信机制,

我的实现逻辑:

  1. 接下来我们要自定义封装一个emitter,什么意思呢,就是模拟一下刚才实现的那个组件通信,我们用到它内置的一个东西,叫做emitter,我们想不用他这个默认的,自己实现他这个机制可不可以,这是我们想做的事情,就是自定义emitter类的封装
  2. 为什么讲它呢,是因为这个东西呢,他呢这里面用到一个设计模式,我们可以把它叫做发布订阅,或者呢叫做自定义事件模式都可以哈,当然了,会学到第二个模式,前面呢我们讲过一个基础模式,就是那个策略模式,不知道大家还有没有印象哈,估计一点印象都没有了,接下来我们继续讲一个模式,叫做设计模式
  3. 第二呢,是这个标题算是不跟鸿蒙进行强绑定的,当然了,他和我们后面要学的混合开发vue,他里面同样会有这个模式,所以呢,他是一个通用的通信机制 ,所以呢给大家做一个拓展,那么目标呢,我们的目的就是从加载开始写,咋写呢,当然大家一点头绪都没有,回想一下我们刚在的用法,我们在莫一个组件,
  4. 比方说A组件,我们是执行了这么一个方法,eimtter.on('事件名',cb)方法,第一个参数是一个事件名,第二个参数我们说了他是一个回调函数callback ,然后呢在B组件里面我们做了这样一个方法调用emitter.emit('事件名')方法,把事件名放过来,这样的话,当B组件调用emit这个方法的时候,我们位于A组件里面的回调函数callback就会被执行,所以呢它整个封装的逻辑啊,是这样的
  5. 其实就是做到叫收集回调函数,然后在触发回调函数的执行,这是我们想做的事儿,第一个叫收集回调函数(on方法),什么意思啊,就是在on方法的时候,你先把你要执行的回调函数放到某一个位置上去,叫做收集回调函数。调用方法调用emit方法的时候,它就是一个把收集到的回调函数在触发起来的一个过程,啊,所以第二呢,就是把收集到的回调函数在执行起来(emit方法),这是我们要实现的事情,只不过我们通过类呢做一个包装,
  6. 接下来我们定义一个类class,我叫他Emitter,既然他有这两个方法,我是不在这里面给他添加这两个方法啊,对吧,你先不用管逻辑,先把这两个方法放进去,一个呢是我们的emit它叫做触发,一个呢是我们的on它呢叫做收集,好先把这两个方法摆过来,接下来我们去关注它们的参数
class Emitter {
  // 收集
  on(){
    
  }
  
  // 触发
  emit(){
    
  }
}

7. on方法呢,是不是接收两个参数啊,第一个参数呢是事件名,第二个参数呢是要执行的回调,那我们在这个地方,第一个参数事件名,我们叫他eventName,他的类型是一个string,第二个参数呢叫做callback,他是一个要执行的一个回调函数,我们可以把它定义成一个Function类型


class Emitter {
  // 收集
  on(eventName:string, cb:Function){
    
  }
  
  // 触发
  emit(){
    
  }
}

8. 好第二个,第二个emit呢也有一个参数,就是一个简单的事件名,这里我们也叫eventName给他一个srting类型,这样的话呢我们就按照他将来调的那个样子呀,我把里面两个方法对外暴露的方法都给写好了,并且呢把他们参数也都给写好了,写好以后呢,咱们分别去实现这两个方法


class Emitter {
  // 收集
  on(eventName:string, cb:Function){
    
  }
  
  // 触发
  emit(eventName:string){
    
  }
}

9. 第一个叫做手机回调函数,是在on方法里面执行的,重点呢在里面是要收集我们的回调就是我们的callback,在这里呢叫做收集回调函数,那既然是要收集的话,我自然要把这个callback存到某一个位置上去对不对,大家思考哈,既然要收集的话那这个callback自然是需要一个地方存下来的位置对吧


class Emitter {
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
  }
  
  // 触发
  emit(eventName:string){
    
  }
}

10. 好第二,那我们将来在调emit方法的时候,还需要找到callback并执行他,那找的时候标题符是eventName,所以呢,callback存的时候还需要跟eventName产生一个关联,好,callback存放的时候,你不能瞎存哈,你要和他前面的第一个参数,eventName做一个绑定关联,


class Emitter {
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联
  }
  
  // 触发
  emit(eventName:string){
    
  }
}

11. 好,这两个分析到位之后呢我们就想,存放emit很简单,在整个这里面,我们只要声明一个地方就可以了,比方说我声明一个属性,我就叫他emitters,是不可以存到这个里面来啊,那怎么往里面存呢?他后面这个数据结构很重要,我到底是要他是个数组呢,还是一个对象呢,还是一个字符串呢,这个就很重要了,那你想,这地方叫callback存放的位置和eventName要对应起来,只要谈到对应,那他必定是一个键值对儿,那就必定是一个对象的数据结构对不对,因为对象的数据结构,天然具有key对应value的这样一个一一对应的关系,那这里的eventName可以把他当成一个key,这里的对应的callback可以把他当成value,那他的对应关系不久形成了吗,所以这个地方呢,就涉及到一个简单的数据结构,我想这样来设计,


class Emitter {
  private emitters
  
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
  }
  
  // 触发
  emit(eventName:string){
    
  }
}

12.这个emitters他是一个对象,key呢就是eventName,value就是对应的callback,{ eventName: cb}我们想以这个方式给他存下来。好,那既然是这样一个存法的话,这里呢先声明一个空对象出来,好,我给他一个Record类型吧,key是一个string,value呢是一个Function,好然后后面呢我们等于一个对象private emitters:Record<string,Function> = {},这是我快速声明一个存放对象的一个结构哈,他的key呢就是{ eventName: cb }这地方的eventName是一个值(string)类型,value是一个Function,好那这个问题呢来告诉我们,怎么在private emitters:Record<string,Function> = {}这里的对象里面来存一个键值对儿啊,可以中括号取值中括号赋值啊,我们通过this.拿到emitters中括号,以我们传下来的eventName作为一个key,以他传下来的callback作为一个value,this.emitters[eventName]=cb这不就是往对象里面填充一个键值对吗,对吧,这个叫做通过中括号给对象里面进行一个key value进行添加或者更改都可以,这叫做用到对象里面你调完this.emitters[eventName]=cb这句话之后,private emitters:Record<string,Function> = {}对象{}里面会多一个他现在是空的,一会呢多一个a对应callback回调函数{ a: cb() },好就这样吧,这叫做对象的中括号赋值法吧,这是on方法里面的


class Emitter {
  private emitters:Record<string,Function> = {}
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
    // 往对象中添加一个新的键值对
    this.emitters[eventName]=cb
  }
  
  // 触发
  emit(eventName:string){
    
  }
}

13.  好再往下,现在呢,我们其实就把他收集起来了,哎收集起来了,第二步,来到emit里面,emit方法无非就是当他emitter.emit('事件名')传过来一个事件名的时候,我要把它eimtter.on('事件名',cb)对应的callback执行一下,这个eimtter.on('事件名',cb)事件名呢是对应的哈,所以呢emit方法这个位置,是不是要中括号取值的呀,这一次就是把他出去的啊,我们叫做使用eventName作为我们对象的key,去取他的value也就是那个callback,然后呢把他执行起来,好那上面this.emitters[eventName]=cb是赋值添加新的键值对,emit这边呢要取了,怎么取啊,是不还是this.emitters拿到我们private emitters:Record<string,Function> = {}这地方的{}大对象,这里面也就有东西了,还是,注意哦emit(eventName:string){这里面eventName:string这个是一个变量哦,变量的话必须是中括号,把eventName拿出来,this.emitters[eventName]这地方取到的是啥,是不就是this.emitters[eventName]=cb这后边的cb(callback)函数,他this.emitters[eventName]是不就可以执行了啊,他既然是个函数,那是不就可以()执行了啊this.emitters[eventName](),到目前最简单的emitter类就已经写好了,一共呢就这么六七行代码,啊非常的简单哈,可能会卡住家人们的就是这一步this.emitters[eventName]=cb


class Emitter {
  private emitters:Record<string,Function> = {}
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
    // 往对象中添加一个新的键值对
    this.emitters[eventName]=cb
  }
  
  // 触发
  emit(eventName:string){
    // 使用eventName作为我们对象的key,去取他的value也就是那个callback,然后呢把他执行起来
    this.emitters[eventName]()
  }
}

14. 好接下来呢,我们可以去验证一下,我这边准备好两个组件,一个A组件和一个B组件,我准备在A里面去触发这个事件,然后在B里面去监听这个事件。


class Emitter {
  private emitters:Record<string,Function> = {}
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
    // 往对象中添加一个新的键值对
    this.emitters[eventName]=cb
  }
  
  // 触发
  emit(eventName:string){
    // 使用eventName作为我们对象的key,去取他的value也就是那个callback,然后呢把他执行起来
    this.emitters[eventName]()
  }
}

@Component
struct Acom {
  build(){
    Button().onClick(()=>{
      // 触发事件
    })
  }
}

@Component
struct Bcom {
  build(){
    Button().onClick(()=>{
      // 监听事件
    })
  }
}

15. 好,那首先呢,我在这里给他进行一个实例化,emitter等于我们的Emitter(), const emitter = new Emitter(),好实列化


class Emitter {
  private emitters:Record<string,Function> = {}
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
    // 往对象中添加一个新的键值对
    this.emitters[eventName]=cb
  }
  
  // 触发
  emit(eventName:string){
    // 使用eventName作为我们对象的key,去取他的value也就是那个callback,然后呢把他执行起来
    this.emitters[eventName]()
  }
}

const emitter = new Emitter()

@Component
struct Acom {
  build(){
    Button().onClick(()=>{
      // 触发事件
    })
  }
}

@Component
struct Bcom {
  build(){
    Button().onClick(()=>{
      // 监听事件
    })
  }
}

16. 接下来呢我们来到B组件页面加载的地方监听,我们来一个emitter点,我们对外的方法就会显示出来了(on方法和emit方法),监听用到那个,是on方法,事件名,我们叫他,家人们你们爱叫他叫啥都行啊,我们叫他cp吧,好value呢是一个Function,()=>{}, 在里面呢我们可以简单log一下,或者food = ()=>{console.log('我是B组件中的foo方法,我被触发了')},在监听事件里面呢我们执行一下我们的food函数,弹窗也是可以的,我这里做了个打印啊,好,这边呢,绑定就结束了


class Emitter {
  private emitters:Record<string,Function> = {}
  // 收集
  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
    // 往对象中添加一个新的键值对
    this.emitters[eventName]=cb
  }
  
  // 触发
  emit(eventName:string){
    // 使用eventName作为我们对象的key,去取他的value也就是那个callback,然后呢把他执行起来
    this.emitters[eventName]()
  }
}

const emitter = new Emitter()

@Component
struct Acom {
  build(){
    Button().onClick(()=>{
      // 触发事件
    })
  }
}

@Component
struct Bcom {
  food = ()=>{
    console.log('我是B组件中的foo方法,我被触发了')
  }
  
  build(){
    Button().onClick(()=>{
      // 监听事件
      aboutToAppear(): void {
       emitter.on('cp',()=>{
         this.food
       })
      }
    })
  }
}

17. 接下来来到触发事件这边,这边是不要触发啊,同样是我们的emitter.emit('cp'), 里面的事件名呢要跟监听事件的事件名一样,也叫cp,好就行了,我们在点的时候看能不能把位于另外一个组件的小回调函数执行起来,打印出来结果


// 自定义Emitter类封装
// emitter
// 1. 设计模式  发布订阅  自定义事件模式
// 2. 通用的通信机制

// A: emitter.on('事件名',cb)   B: emitter.emit('事件名')
// 1. 收集回调函数  2. 然后再触发回调函数的执行


// 优化:一对一的关系  变成一对多的关系
// { eventName: cb } -> {eventName: [cb1,cb2] }

// 优化:除了触发事件之外,还期望可以传递参数过去

class Emitter {
  private emitters:Record<string,Function[]> = {}

  on(eventName:string, cb:Function){
    // 收集回调函数
    // 1. cb需要一个存放的位置
    // 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
    // 往对象中添加一个新的键值对

    // 首次添加的事件的时候 先初始化一个 {eventName: []}
    if(!this.emitters[eventName]){
      // 没有初始化过
      this.emitters[eventName] = []
    }

    this.emitters[eventName].push(cb)
  }

  emit(eventName:string, arg?:string){
    // 使用eventName作为对象的key去取它的value也就是那个cb,然后把它执行起来
    this.emitters[eventName].forEach(cb=>{
      cb(arg)
    })
  }
}

const emitter = new Emitter()


@Component
struct ACom {
  build() {
    Button('触发事件').onClick(()=>{
      // 触发事件
      emitter.emit('cp','我是来自A组件中的参数')
    })
  }
}

@Component
struct BCom {
  foo = (arg:string)=>{
    console.log('我是B组件中的foo方法,我被触发了',arg)
  }

  aboutToAppear(): void {
    // 监听事件
    emitter.on('cp',(arg:string)=>{
      this.foo(arg)
    })
    emitter.on('cp',(arg:string)=>{
      console.log('我是另外一个需要触发的函数',arg)
    })
  }

  build() {
  }
}


@Entry
@Component
struct TestPage {
  build() {
    Row(){
      ACom()
      BCom()
    }
  }
}
​


©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS

暂无评论数据

发布

头像

K老师

大家好我是K老师,这是我的个人介绍:鸿蒙先锋,鸿蒙开发者达人,鸿蒙应用架构师,HDG组织者,可0-1开发纯血鸿蒙应用,可0-1开发前端加鸿蒙混合应用,可0-1开发PC端鸿蒙应用。

118

帖子

0

提问

1412

粉丝

关注
热门推荐
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 商务内容合作QQ:2291221 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除
京ICP备:2022009079号-2
京公网安备:11010502051901号
ICP证:京B2-20230255