如何实现MobX派生/computed值的分层因果变更日志
MobX派生值网络同构变更日志构建问题
我搭建了一套运行流畅的MobX派生值网络,目前希望在单个事务上下文内,面向用户可视化部分变更,以及触发对应变更的上游依赖,即构建与发生变更的computed树在结构上同构的数据结构,实现分层日志展示,示例效果如下:
在使用MobX之前,我通过自定义update函数实现计算逻辑,可以依靠调用栈、通过入栈出栈维护日志上下文的方式构建同构日志(多数场景下可以默认update执行时就有需要记录的变更,部分场景会缓存旧值做比对),示例代码如下:
updateA(){ pushLog("A") // 创建"A"日志节点 if (...) updateX() else (...) updateB(); popLog() } updateB(){ pushLog("B") // 创建"B"日志节点 updateC() if (...) updateX() popLog(); // 弹出B日志节点,所有节点按入栈出栈规则正确嵌套 } updateC(){ pushLog("C") // 对应计算逻辑 popLog(); } updateX(){ pushLog("X") // 对应计算逻辑 popLog(); }
切换MobX后遇到的卡点
使用MobX的computed和reaction能力后,没有清晰的同步调用栈,无法沿用旧方案正确构建同构日志树,具体问题如下:
- 若为每个computed创建独立reaction:reaction会在事务末尾批量执行,既无确定执行顺序,也无法获取完整因果链路信息
- 若在事务结束后组装日志树:记录每个computed的调试名,在reaction中做依赖查找,判断已有日志条目是否为当前computed的依赖,再将新日志条目挂载到对应父节点下——但日志本身是observable,事务结束后更新日志会触发额外的重算逻辑,不符合预期,理想状态下触发computed变更的事务本身就应包含日志的变更
- 若直接在computed计算函数内部打日志:当computed存在多个依赖时,无法确认具体是哪个/哪些依赖触发了本次computed重算,若自行缓存旧值做比对,又和MobX已有的变更检测能力重复,代码冗余度高
实际应用场景
很多桌游的规则可以用函数式派生逻辑表达,例如《卡坦岛》中,玩家是否获胜是总点数的派生值,总点数是玩家连接道路数量(及其他因素)的派生值。
当player.won == true时触发胜利UI提示很容易实现,但理想效果是展示胜利的完整因果链路:例如胜利是因为获得了最长路,获得最长路是因为玩家放置了道路,层级展示效果如下:
放置道路
获得「最长路」!+10点
胜利!
要展示这类因果关系,不仅需要知道初始变更(放置道路),还需要知道触发player.won变更的完整派生链。当初始变更到最终结果的依赖树存在多条路径时,该能力尤为必要:例如某卡坦岛变体规则中,放置道路可能形成所有对手都被封锁的局面,直接判定默认胜利,对应链路为:
放置道路
所有对手被封锁
胜利!
内容的提问来源于stack exchange,提问作者Ian
相关产品推荐
相关产品推荐

