“选一个场站,下面的图表一起刷新。”这类需求我以前很容易直接想到事件总线,导航器发事件,图表订阅,接起来就能跑。最近越来越多参与需求、交互和排期,发现需要解释的事情发生在事件之前。
首次进入选什么,清空表示全部还是不查,保存后怎样恢复,换组织导致原设备不可见时怎么办,这些都不是组件各自猜一个默认值就能解决的。
页面需要一份共同状态
导航器是参数输入来源,地图、指标卡和趋势图则依赖相同参数。页面保存已经确认的状态,组件读取状态快照。这样从链接进入、恢复已保存页面和手动点选,可以进入同一套处理规则。
选择至少区分未选、当前范围全部和明确资源列表,空数组不承担几种相反含义。方案采用下面这一份声明,页面版本和选择一起提交:
type Selection =
| {kind: "none"}
| {kind: "all-in-scope"; scopeId: string}
| {kind: "ids"; ids: string[]};
type PageState = {revision: number; selection: Selection};all-in-scope 表示当前身份在某范围内的全部资源,不是跳过权限检查。接口只接收资源列表时,由适配层解析选择,不能让不同接口各自猜测空值。
切换一次,提交一份一致快照
从场站 A 切到 B,旧设备 A-1 如果不属于 B,就同时清空设备选择。不能场站先显示 B、图表仍按 A-1 请求。所有下游参数计算完成后一次提交 PageState,revision 随之变化。
图表请求携带所用 revision,只允许匹配当前版本的响应更新。取消旧请求减少浪费,版本比较保证正确性。旧请求无法取消、或者取消时已经返回,也不会覆盖新状态。
加载和错误状态同样关联版本。旧请求的 finally 不能关掉新请求的 loading,一张图失败也不能继续留着上个场站的数据,却不说明它已经过期。
联动图需要明确方向
导航器更新参数,参数驱动图表,图表点击又可能更新另一个导航器。如果同值更新继续广播,或者来源不明确,就容易形成循环。
规范化后相同的状态不重复传播,变更保留来源与版本。依赖是有向无环图时,按拓扑顺序计算受影响参数,得到完整快照后统一提交,不在计算中间逐个触发请求。有环且不支持迭代,就在配置时报告循环路径;支持迭代则限定次数和收敛条件,超出限制保留错误,不能无限传播。
配置界面也应能查到某组件的值来自哪个参数。用户看得到绑定关系,排查才能从“图表怎么又变了”回到实际输入。
验收写到具体的用户行为
我会固定这些情况:首次进入、快速 A 到 B、清空、恢复保存状态、原资源失去权限、某图失败,以及带筛选条件重新打开链接。每项都约定显示内容、请求参数与保留状态。
例如 revision=5 发出 A 请求,切 B 提交 revision=6,晚到的 A 结果丢弃。B 查询失败,图表显示 B 的失败状态,不能把 A 的数据当成当前结果。这一条行为就同时约束了产品、接口和渲染。
转向产品以后,技术经验仍能帮助我判断实现,但还有一项工作:把代码之前的决定写清楚。状态、空值和错误行为有共同约定,研发和测试就不必在联调时继续猜这句话的意思。