
一、一次点击换来三张图的不同反应镇里那面大屏有三米宽左边一张地图铺满上面标着辖区内十四个村的点位右边竖着三张图产量趋势、人口结构、积分排行。需求很朴素鼠标点某个村的点位右边三张图同时换成这个村的数据。做第一版的时候我们想当然地让点位组件直接去调那三张图。页面上的组件一共十四个除了地图和三张图还有滚动的通知条、指标卡、视频窗、弹窗列表。第一版做完之后组件之间的调用关系画出来是一张网互相引用的连线有十七条改一个图表的数据字段名要跟着改三处调用方。那个版本上线两周出现了三次同一个毛病。点一次点位产量图刷新了人口图没动排行图刷了两遍。排查下来是弹窗里也放了一份图表它在组件销毁时没有把之前的调用关系清理掉一次点击触发了多路响应。二、依赖的方向比组件写得好不好更要紧拆开看问题不在组件写得好不好在依赖的方向。地图不该知道右边有几张图、图里画的是什么字段它只知道用户选了哪个村。图表也不该知道点位是从哪里来的它只知道现在要展示哪个村的数据。两端各少知道一点耦合就断开了。直觉做法是父组件统管把数据和回调都放在页面顶层一层层往下传。这在小页面上很干净一旦层级深了就会出现层层透传中间的容器组件自己不用这些数据却要为下面几层声明一堆属性。另一个直觉是全局状态把选中的村放进共享状态里图表监听它。这条路可行代价是任何一个字段变化都会触发一批订阅者去判断而大屏上的状态有很多刷新频率也不一样混在一份状态里容易互相牵着走。这三个做法我们都试过一小段。第一版用的是直接调用第二版换成全局状态跑起来之后发现大屏上有三块内容并不需要跟着点位变比如滚动通知和视频窗它们在状态变化时也会被通知一遍虽然每次判断很轻但一个下午的观察里它们的重绘次数从零变成了两百多次。三、三条路和各自的代价第一条路是组件直接互相调用通过引用拿到对方的方法。实现最短一个事件加一个引用就能通缺点是依赖方向被写死在组件里组件一多就成了网状结构新增一块图表要回到点位组件里改代码。第二条路是全局状态管理。所有组件从同一份状态里读。好处是数据来源只有一处调试时能一眼看到当前选中了谁坏处是状态粒度不好切粗了会连坐细了要维护一堆订阅关系而且它更适合描述状态不太适合表达一次动作。第三条路是事件总线。发布者只管抛出一次动作并带上载荷订阅者各自决定要不要响应、怎么响应。地图不知道有几张图图表也不知道点位在哪。代价是链路变隐式出问题要靠日志和事件名回溯所以得配一份事件名常量表和一份订阅登记否则时间长了没人说得清有哪些人在听。四、事件名常量表与载荷我们选了事件总线配一份事件名常量表。所有事件名写成常量文本只在一处定义抛出和接收都引用常量拼错的时候构建阶段就能拦住不用等运行时才发现没人响应。载荷是一个约定好的对象里面三个字段就够用。scope 放区划码metric 放指标名range 放时间区间。发布者把自己知道的那部分填上订阅者按需取字段不需要的字段不读也不假定别的订阅者读了什么。组件订阅的时候把销毁函数登记下来销毁时调用一次反注册。这套事件约定后来写进了万村乐数字乡村的大屏开发规范新组件按约定接入就行。规范里只有一页写清楚事件名怎么起、载荷怎么填、销毁时做什么。五、载荷结构、登记与批量派发一次选择的载荷长这样区划码是十二位村级码指标名用枚举时间区间是起止两个时间戳。如果某个订阅者只需要区划它读 scope 就够了需要看指标的读 metric需要按区间算的读 range。字段是稳定的加字段只往后加不改已有字段的含义。派发改成微任务批量处理。一次选择可能连着抛出两三个事件比如选中村之后要同时通知数据刷新和标题更新如果同步逐个调用渲染线程会被占住点位的高亮要等所有订阅者跑完才出现肉眼看得出有延迟。六、三个坑的现场记录第一个坑是切页之后旧组件没有反注册。现象是从大屏切到台账页再切回来点一次点位图表刷新两遍。根因是组件销毁时只销毁了实例没把当初登记的订阅清掉总线里还留着上一份回调。改法是订阅时把销毁函数返回出来销毁钩子里统一调用并且给总线加一个按页面标识批量清理的入口切页时兜底清一次。第二个坑是事件风暴。现象是拖动画布连续选村的时候图表被刷新了几十次接口也发出了几十个请求。根因是每次选择都立刻抛事件而订阅者的响应里有网络请求。改法是在发布侧做合并两百毫秒内连续的同类事件只保留最后一次同时在订阅侧对请求做取消新的进来时把上一个未完成的取消掉。第三个坑是同步派发阻塞渲染。现象是点位高亮比图表刷新慢半拍点下去要过三四百毫秒才变色。根因是事件用同步方式逐个调用订阅者订阅者里又有同步计算主线程被占满。改法是把派发改成微任务先把高亮和交互状态更新完再批量执行订阅者的逻辑计算量大的订阅者自己往下一帧放。七、解耦之外的两件事事件总线解决的是解耦解决不了数据的一致性。三张图分别向不同接口取数取到的时刻不一样用户看到的就是三个人口数的短暂时差。这是异步取数本身的特性我们只能把同一屏用到的数据合成一次请求来减少时间差不能消除它。它也管不了谁该响应。事件是广播的任何订阅者都能听如果没有约定新来的同事可能给一个与它无关的事件挂上订阅短期内看不出问题出了问题也很难归因所以约定和评审比总线本身更要紧。八、每块大屏都靠这套总线串起来现在这套总线在开发上的好处是加一块图表不用去动地图那块代码。新组件在挂载时声明自己关心哪个事件取到载荷自己去拿数据删掉它的时候别人也不受影响改动的范围被压在一个文件里。现在万村乐数字乡村的每块大屏都靠这套事件总线串起来最复杂的一屏挂了十四个组件。约定那一页纸从写完到现在没改过倒是新组件一直在往里加。