我是从 tsrpc 这个项目中第一次看到这种用法,这种无论怎样,都是要有一个数据和消息的关连关系来辅助类型推断。
要么在消息数据体 data 中,要么是对应关系映射表。
其中映射表的方式除了集中式(事件一般是集中式)。还可以用 global declare {
interface XX {
[event_name]: event_data_type
}
}
进行分布式定义。而这种机制可以泛化到【类型到类】【类型到函数】【类型到类型】,这种机制的复用让理解成本更低。
我是从 tsrpc 这个项目中第一次看到这种用法,这种无论怎样,都是要有一个数据和消息的关连关系来辅助类型推断。
要么在消息数据体 data 中,要么是对应关系映射表。
其中映射表的方式除了集中式(事件一般是集中式)。还可以用 global declare {
interface XX {
[event_name]: event_data_type
}
}
进行分布式定义。而这种机制可以泛化到【类型到类】【类型到函数】【类型到类型】,这种机制的复用让理解成本更低。
TS的类型比C#要强大,很多C#办不到或很难办到的,用TS就比较好实现。
按我的理解事件系统是为了解决“你中无我,我中无你,但是我们俩还想通信”,或者是为了达到“你中无我,我中无你”的状态而引入事件系统以求解耦。还有一种情况比较简单“你中有我,但是我中不能有你,因为可能循环引用”,虽然可以用接口去解决,但是也可以用事件系统达到目的。
题主的事件系统能搞这么复杂,也是让人头皮发麻,没有看下去的欲望,我一度以为我理解的事件系统和题主理解的不是一回事,我都懒得造轮子,感觉造出来也没啥意义,直接用的EventEmitter3,只是简单封装了下,封装的目的是为了方便使用仅此而已。
源码:
我觉得这就够了,而且type申明的类型构建后也擦除了
这个类型 最后其实就是直接调用了那个函数本身 不算是发消息就是找到那个对象的地址直接发消息差不多的样子,事件系统一般就是向世界抛出一个事件谁监听了这个时间 谁就可以获取到这个事件的数据
直接用game.emit 感觉一样的呀 
就是为了解耦才用事件,结果还给事件类型耦合起来…
楼主是为了解决数据类型约束,不过方案过于复杂
耦合的主体有区别,你说的是调用主体解耦。楼主说的是事件派发的数据类型约束
你这个 IEvent 类型方案其实和 MK 的类似,我以前尝试过,结论是:不好用。会有几个问题点
1、集中式的声明。有些逻辑其实是分包的,这个 IEvent 没有支持分布式定义。(当然 declare global 可解决这个问题)
2、所有事件都要声明(这个比较蛋疼,越用越难受)
3、事件处理函数有时候是多个函数。有的函数只心事件,不关心参数,通常是类似 refreshUI() 的刷新函数,而注册时就会报红,导致必须加 as any 这类的特殊处理。难受。
我的方案就是,通过约定做隐式数据约束。
emit<DATA = any>(name: string | number, data: DATA = null)
on<DATA = any>(name: string | number, cb: (data?: DATA) => any, ctx: any): EventBox;
于是,在开发的时候:
1、派发的地方有数据,前面的类型一定要补上,方便知道是什么数据类型;
2、监听的地方,一定根据派发的数据约束写上数据约束(不写表示只关心事件本身);
3、在新增消息处理函数的时候,只要查一下别的地方怎么用,然后做同样的约束。
4、派发数据更改时(重构)的时候。也是查下消息的引用。统一修改各个监听函数的处理逻辑的。
这套逻辑流程用起来最舒服。
ikun 2.5 year

最佩服一个事件系统都可以有自己一套理论的人了 
两个emitter 更难用
我只能说明你的不好用是针对你自己的,原因如下
分布式定义事件类型是最差的方案,因为不能一眼看到有什么事件,只有自己知道或者利用编辑器查看引用,这本身就是浪费时间和协作开发的拖累
事件类型只需要定义一次,和 Enum 方案相比只是多了几个参数但是保证了类型安全
事件必须关注参数,除非你不用事件参数,或者不在乎事件参数类型和数量是否正确
你的方案就是直接忽略事件参数校验,我可以随便传递任何类型的事件参数但是不会在编辑器报错,只有运行时报错
最最佩服的是拿这套理论去成功说服团队使用 
给你看个以前开发过的游戏的集中定义的例子。400 多个事件同一个地方定义。就算一眼看(那又能看出什么):
分布式有分布式的好处。第一个就是,分布式方案如果只有一个地方,那就退化成集中式的。它是集中定义方案的超集。
框架层事件和业务事件可以分工。A 模块和 B 模块的人可以协作各自定义各自的事件。你的意思是 2 个要写一起?
还是说,你理解的分布式定义是某个文件里只定义一个事件的那种?
你的上面的问题是没有划分事件粒度,比如全局事件,子游戏事件,模块事件。所有事件放在一起就会像你举例那样
我之前的确以为你说的是每个模块把公共事件放在自己模块脚本内。所以这里误解了,一眼看到事件类型的优势是可以方便自己找到其他人已经写好的事件使用,但是事件太多也是一个问题
而我之前举例的 EventTarget 是一个类型,可以根据自己需要实例化 N 个对象,我自己使用也是分全局和单独的模块或者子游戏事件对象,所以是否造成事件堆积看自己怎么使用
我懂了,你还有分模块的多个 EventTarget 对象。那的确是可以用这种方式。
看了这么多事件系统,其实有一点我觉得要重视一下,就是在dispatch过程中,处理函数remove 自己(或其他同事件)的情况,会让外层派发循环index偏移,跳过1-n个事件触发。
解决办法很多也很简单,在事件派发中触发remove的时候,给要移除的handler记一个删除标志,等派发完成后删除,就可以解决。不过我看大家都没做这个处理。