[干货][HarmonyOS 鸿蒙Next][都来看看这个干货太干了]关于我自己封装了emitter,我是这么实现的。
[干货][HarmonyOS 鸿蒙Next][都来看看这个干货太干了]关于我自己封装了emitter,我是这么实现的。 提示:家人们如果觉得太多的话可以看前面的思路,然后直接跳到最后面复制代码到dev里面运行即可,代码主要的我都注释了
我在写鸿蒙的一个工具类项目的时候想自己封装一个emitter挑战一下,用来实现是否已学习这个状态的时候,使用emitter.on 触发事件,emitter.emit 监听事件,emitter是用到里面的设计模式,或者可以叫做发布订阅/自定义事件模式,还有一种模式,策略模式,混合开发同样会有这一套模式,所以他是一套通用的通信机制,
我的实现逻辑:
-
接下来我们要自定义封装一个emitter,什么意思呢,就是模拟一下刚才实现的那个组件通信,我们用到它内置的一个东西,叫做emitter,我们想不用他这个默认的,自己实现他这个机制可不可以,这是我们想做的事情,就是自定义emitter类的封装
-
为什么讲它呢,是因为这个东西呢,他呢这里面用到一个设计模式,我们可以把它叫做发布订阅,或者呢叫做自定义事件模式都可以哈,当然了,会学到第二个模式,前面呢我们讲过一个基础模式,就是那个策略模式,不知道大家还有没有印象哈,估计一点印象都没有了,接下来我们继续讲一个模式,叫做设计模式
-
第二呢,是这个标题算是不跟鸿蒙进行强绑定的,当然了,他和我们后面要学的混合开发vue,他里面同样会有这个模式,所以呢,他是一个通用的通信机制 ,所以呢给大家做一个拓展,那么目标呢,我们的目的就是从加载开始写,咋写呢,当然大家一点头绪都没有,回想一下我们刚在的用法,我们在莫一个组件,
-
比方说A组件,我们是执行了这么一个方法,eimtter.on(‘事件名’,cb)方法,第一个参数是一个事件名,第二个参数我们说了他是一个回调函数callback ,然后呢在B组件里面我们做了这样一个方法调用emitter.emit(‘事件名’)方法,把事件名放过来,这样的话,当B组件调用emit这个方法的时候,我们位于A组件里面的回调函数callback就会被执行,所以呢它整个封装的逻辑啊,是这样的
-
其实就是做到叫收集回调函数,然后在触发回调函数的执行,这是我们想做的事儿,第一个叫收集回调函数(on方法),什么意思啊,就是在on方法的时候,你先把你要执行的回调函数放到某一个位置上去,叫做收集回调函数。调用方法调用emit方法的时候,它就是一个把收集到的回调函数在触发起来的一个过程,啊,所以第二呢,就是把收集到的回调函数在执行起来(emit方法),这是我们要实现的事情,只不过我们通过类呢做一个包装,
-
接下来我们定义一个类class,我叫他Emitter,既然他有这两个方法,我是不在这里面给他添加这两个方法啊,对吧,你先不用管逻辑,先把这两个方法放进去,一个呢是我们的emit它叫做触发,一个呢是我们的on它呢叫做收集,好先把这两个方法摆过来,接下来我们去关注它们的参数
class Emitter {
// 收集
on(eventName: string, cb: Function){
}
// 触发
emit(eventName: string){
}
}
- on方法呢,是不是接收两个参数啊,第一个参数呢是事件名,第二个参数呢是要执行的回调,那我们在这个地方,第一个参数事件名,我们叫他eventName,他的类型是一个string,第二个参数呢叫做callback,他是一个要执行的一个回调函数,我们可以把它定义成一个Function类型
class Emitter {
// 收集
on(eventName: string, cb: Function){
}
// 触发
emit(eventName: string){
}
}
- 好第二个,第二个emit呢也有一个参数,就是一个简单的事件名,这里我们也叫eventName给他一个srting类型,这样的话呢我们就按照他将来调的那个样子呀,我把里面两个方法对外暴露的方法都给写好了,并且呢把他们参数也都给写好了,写好以后呢,咱们分别去实现这两个方法
class Emitter {
// 收集
on(eventName: string, cb: Function){
}
// 触发
emit(eventName: string){
}
}
- 第一个叫做手机回调函数,是在on方法里面执行的,重点呢在里面是要收集我们的回调就是我们的callback,在这里呢叫做收集回调函数,那既然是要收集的话,我自然要把这个callback存到某一个位置上去对不对,大家思考哈,既然要收集的话那这个callback自然是需要一个地方存下来的位置对吧
class Emitter {
// 收集
on(eventName: string, cb: Function){
// 收集回调函数
// 1. cb需要一个存放的位置
}
// 触发
emit(eventName: string){
}
}
- 好第二,那我们将来在调emit方法的时候,还需要找到callback并执行他,那找的时候标题符是eventName,所以呢,callback存的时候还需要跟eventName产生一个关联,好,callback存放的时候,你不能瞎存哈,你要和他前面的第一个参数,eventName做一个绑定关联,
class Emitter {
// 收集
on(eventName: string, cb: Function){
// 收集回调函数
// 1. cb需要一个存放的位置
// 2. cb存放的时候 和eventName做绑定关联
}
// 触发
emit(eventName: string){
}
}
- 好,这两个分析到位之后呢我们就想,存放emit很简单,在整个这里面,我们只要声明一个地方就可以了,比方说我声明一个属性,我就叫他emitters,是不可以存到这个里面来啊,那怎么往里面存呢?他后面这个数据结构很重要,我到底是要他是个数组呢,还是一个对象呢,还是一个字符串呢,这个就很重要了,那你想,这地方叫callback存放的位置和eventName要对应起来,只要谈到对应,那他必定是一个键值对儿,那就必定是一个对象的数据结构对不对,因为对象的数据结构,天然具有key对应value的这样一个一一对应的关系,那这里的eventName可以把他当成一个key,这里的对应的callback可以把他当成value,那他的对应关系不久形成了吗,所以这个地方呢,就涉及到一个简单的数据结构,我想这样来设计,
class Emitter {
private emitters: Record<string, Function> = {}
// 收集
on(eventName: string, cb: Function){
// 收集回调函数
// 1. cb需要一个存放的位置
// 2. cb存放的时候 和eventName做绑定关联 { eventName: cb }
}
// 触发
emit(eventName: string){
}
}
- 这个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){
}
}
- 好再往下,现在呢,我们其实就把他收集起来了,哎收集起来了,第二步,来到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.emitterseventName,到目前最简单的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]()
}
}
- 好接下来呢,我们可以去验证一下,我这边准备好两个组件,一个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](/user/Component)
struct Acom {
build(){
Button().onClick(()=>{
// 触发事件
})
}
}
[@Component](/user/Component)
struct Bcom {
build(){
Button().onClick(()=>{
// 监听事件
})
}
}
- 好,那首先呢,我在这里给他进行一个实例化,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](/user/Component)
struct Acom {
build(){
Button().onClick(()=>{
// 触发事件
})
}
}
[@Component](/user/Component)
struct Bcom {
build(){
Button().onClick(()=>{
// 监听事件
})
}
}
- 接下来呢我们来到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](/user/Component)
struct Acom {
build(){
Button().onClick(()=>{
// 触发事件
})
}
}
[@Component](/user/Component)
struct Bcom {
food = () =>{
console.log('我是B组件中的foo方法,我被触发了')
}
build(){
Button().onClick(()=>{
// 监听事件
aboutToAppear(): void {
emitter.on('cp',()=>{
this.food
})
}
})
}
}
- 接下来来到触发事件这边,这边是不要触发啊,同样是我们的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](/user/Component)
struct ACom {
build() {
Button('触发事件').onClick(()=>{
// 触发事件
emitter.emit('cp','我是来自A组件中的参数')
})
}
}
[@Component](/user/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](/user/Entry)
[@Component](/user/Component)
struct TestPage {
build() {
Row(){
ACom()
BCom()
}
}
}
更多关于[干货][HarmonyOS 鸿蒙Next][都来看看这个干货太干了]关于我自己封装了emitter,我是这么实现的。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
请问在有多个订阅的情况下,如何取消 emitter的某一个指定的订阅而不影响其他页面的订阅接收?
更多关于[干货][HarmonyOS 鸿蒙Next][都来看看这个干货太干了]关于我自己封装了emitter,我是这么实现的。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
感谢分享
谢谢分享关于自己封装的Emitter使用方法,请问一下这个Emitter具体使用场景有哪些?可以跨组件或者父子组件间使用吗?
您好,都是可以,可以在不同页面通信,可以跨组件,跨线程,父子当然也可以,父子之间的话可以@Prop就行,我这个是封装来挑战自己的,想要更全面的可以直接用官方的Emitter,官方的Emitter也很好用的也全,我这个提供封装的思路和一些使用。
针对帖子标题中提到的“关于我自己封装了emitter,我是这么实现的”这一问题,在鸿蒙(HarmonyOS)开发环境下,封装emitter通常涉及事件发布与订阅机制的自定义实现。以下是基于鸿蒙开发框架的简要回答:
在鸿蒙系统中封装emitter,你可能需要利用鸿蒙提供的事件和通信机制。首先,定义一个事件类来封装要传递的数据。然后,创建一个事件管理器或中心,负责事件的发布和订阅管理。在发布事件时,事件管理器会将事件数据派发给所有订阅了该事件的监听器。
实现过程中,你可能需要设计一套灵活的订阅机制,允许监听器根据事件类型或具体条件进行订阅。同时,考虑到鸿蒙系统的跨设备协同能力,你的emitter实现可能需要支持在不同设备间传递事件,这可能需要利用鸿蒙的分布式通信能力。
此外,确保你的emitter实现是线程安全的,特别是在多线程环境下使用时。你可能需要使用鸿蒙提供的同步机制来保证事件发布和订阅的原子性和一致性。
如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html,