news 2026/8/24 11:55:17

Diode Action Processor 中间件进阶:动作日志、Undo撤销栈与RAF批处理渲染3大实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Diode Action Processor 中间件进阶:动作日志、Undo撤销栈与RAF批处理渲染3大实战

Diode Action Processor 中间件进阶:动作日志、Undo撤销栈与RAF批处理渲染3大实战

【免费下载链接】diodeScala library for managing immutable application model项目地址: https://gitcode.com/gh_mirrors/dio/diode

Diode 是一个用 Scala 编写的不可变应用状态管理库,它的核心机制Action Processor(动作处理器,本质上就是"中间件")可以拦截每一个被派发的 Action,实现日志记录、撤销栈、批量渲染等高级功能。本文带你用 3 个实战场景,快速掌握 Action Processor 中间件的用法与最佳实践,适合已了解 Diode 基础概念的新手读者。

什么是 Action Processor:Diode 的中间件机制

在 Diode 中,所有 Action 都会经过 Circuit 的处理器链(processing chain),然后才交给 Action Handler 处理。Action Processor 就是插入在这条链上的"关卡":

  • 拦截动作:在 Action 被处理前,你可以记录、修改甚至取消它;
  • 拦截结果:处理完成后,你还能拿到 Handler 返回的新 Model,把它压入撤销栈;
  • 可插拔:随时通过addProcessor/removeProcessor添加或移除,不污染业务代码。

ActionProcessor的核心接口只有一个process方法,接收当前模型和一个next回调(继续调用链上的下一个处理器):

trait ActionProcessor[M <: AnyRef] { def process(dispatch: Dispatcher, action: Any, next: Any => ActionResult[M], currentModel: M): ActionResult[M] }

接口定义在 Circuit.scala。

实战一:动作日志中间件 —— 如何记录每一个 Action

调试状态管理应用时,最常问的问题就是"模型为什么变成这样了?"。一个日志中间件能完美回答它。

官方文档给出的经典实现LoggingProcessor只有几行核心逻辑:在process里把带时间戳的动作记入日志,然后调用next(action)放行:

class LoggingProcessor[M <: AnyRef] extends ActionProcessor[M] { var log = Vector.empty[(Long, String)] override def process(dispatch: Dispatcher, action: Any, next: Any => ActionResult[M], currentModel: M) = { log = log :+ (System.currentTimeMillis(), action.toString) next(action) } }

注册到 Circuit 上即可生效,不需要任何改动视图或 Handler 的代码:

val logProcessor = new LoggingProcessor[RootModel] AppCircuit.addProcessor(logProcessor)

💡 提示:如果日志不再需要,用removeProcessor移除即可,处理器链会自动重建。

完整的用法与更多示例见官方文档:ActionProcessors.md。

实战二:Undo 撤销栈 —— 拦截新模型实现撤销重做

撤销功能的精髓在于:在动作处理完后,把旧模型存起来。这正是 Action Processor 的拿手好戏,因为它能看到 Handler 处理前的currentModel和处理后的新 Model。

实现思路分三步:

  1. 存入历史:每次有实际修改模型的动作通过时,把currentModel压入 undo 栈(并清空 redo 栈);
  2. 定义 Undo 动作:派发Undo时,从栈顶弹出一个旧模型,直接返回ActionResult.ModelUpdate(旧模型)
  3. 重做同理Redo动作把旧模型挪回 redo 栈并恢复栈顶新模型。
override def process(dispatch: Dispatcher, action: Any, next: Any => ActionResult[M], currentModel: M) = { action match { case Undo if undoStack.nonEmpty => redoStack = currentModel :: redoStack ActionResult.ModelUpdate(undoStack.head) case _ => val result = next(action) // 只有模型真的变了才记录历史 if (result.newModelOpt.isDefined) undoStack = currentModel :: undoStack result } }

🎯 关键技巧:判断newModelOpt是否非空,可以避免把"无变化"的动作也塞进撤销栈,保证撤销体验干净利落。

实战三:RAF 批处理渲染 —— 让浏览器动画跑满 60fps

这是 Action Processor 最惊艳的实战场景。在浏览器里做动画,正确的姿势是用requestAnimationFrame(RAF):它保证回调以显示器刷新率(通常 60Hz)执行,并提供精确的时间戳。

问题在于:动画每秒会派发上百个"更新动画"的动作,如果每个动作都触发一次视图更新,渲染开销巨大。Diode 的 RAF 示例给出了答案——一个RAFBatcher 批处理中间件

  1. 标记动作:定义一个标记 traitRAFAction,凡继承它的动作都会被拦截;
  2. 攒批:中间件把这类动作包进RAFWrapper存入批次列表,并请求一帧 RAF,本帧内不处理;
  3. 集中派发:RAF 回调触发时,先派发一个携带精确时间的RAFTimeStamp动作,再把整批动作以ActionBatch一次性派发;
  4. 合并通知:Circuit 对批量动作做了内部优化——所有动作处理完才调用一次监听器,视图只渲染最终结果。
case class UpdateAnimation(id: Int) extends RAFAction // 被批处理 case class RAFTimeStamp(time: Double) extends Action // 携带帧时间

批处理器的完整实现见 RAFBatcher.scala,配套的电路实现(如何把时间戳写入模型、动画如何自驱动)见 AppCircuit.scala,运行效果可在 RAF 示例中查看:examples/raf/。

💡 进阶建议:即使没有动画,如果你的应用频繁派发大量小动作,批处理策略同样能显著提升性能。

上手指南:添加与移除 Processor 最快配置

把中间件挂到 Circuit 上只需两行代码,整个生命周期完全可控:

// 添加:处理器链自动重建,新处理器位于链的最前端 AppCircuit.addProcessor(myProcessor) // 移除:不再需要时随时摘除,不影响其他处理器 AppCircuit.removeProcessor(myProcessor)

添加、移除的底层实现(加锁 + 重建处理链)见 Circuit.scala。

更多扩展场景:持久化应用状态

Action Processor 的应用远不止上面三种。Diode 官方 devtools 模块就提供了一个PersistState 处理器:捕获Save(id)/Load(id)两个动作,把当前模型序列化后保存;加载时把异步 IO 包成Effect,完成后派发内部Loaded动作真正更新模型。

这在开发中排查 bug 时特别有用——遇到异常状态先存个快照,修完代码重载应用后一键恢复到出问题的现场。完整实现见 PersistState.scala。

总结:3 大实战场景速查表

实战场景核心手法关键代码位置
📝 动作日志记录后next(action)放行ActionProcessors.md
🔄 Undo/Redo 撤销栈处理前存旧模型,ModelUpdate恢复ActionProcessors.md
🎞️ RAF 批处理渲染标记动作 + 攒批 +ActionBatch一次性派发RAFBatcher.scala

掌握 Action Processor 中间件机制后,Diode 就从"状态管理库"升级成了可扩展的应用框架。建议你从日志中间件写起,再逐步挑战撤销栈与 RAF 批处理,三个场景循序渐进,覆盖绝大多数生产需求。

【免费下载链接】diodeScala library for managing immutable application model项目地址: https://gitcode.com/gh_mirrors/dio/diode

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 11:54:16

网站合规必备:legal-templates使用条款与Cookie声明怎么搭配用

网站合规必备&#xff1a;legal-templates使用条款与Cookie声明怎么搭配用 【免费下载链接】legal-templates Public collection of high-quality templates created by General Legal attorneys with CC0 license 项目地址: https://gitcode.com/gh_mirrors/le/legal-templa…

作者头像 李华
网站建设 2026/8/24 11:51:26

工业上位机通讯协议入门:从零理解Modbus到代码实现

第一次打开一个工业控制软件&#xff0c;或者第一次面对 PLC 和电脑之间的数据线时&#xff0c;很多人都会问&#xff1a;“我该怎么让它们‘说话’&#xff1f;” 你可能会去搜索“C# 上位机如何读取 PLC 数据”&#xff0c;然后得到一堆关于串口、Socket、Modbus 的代码片段。…

作者头像 李华
网站建设 2026/8/24 11:50:49

数学建模国赛C题实战:随机动态规划在供应链优化中的应用

1. 从“解题”到“建模”&#xff1a;一次国赛C题的深度复盘与实战指南又到了一年一度数学建模国赛季&#xff0c;看着学弟学妹们开始组队、找资料、刷真题&#xff0c;我总会想起几年前自己带队鏖战三天三夜&#xff0c;最终拿下国赛C题奖项的经历。很多人把数学建模看作是一次…

作者头像 李华
网站建设 2026/8/24 11:49:49

数学建模核心模型解析:从线性回归到动态规划的实战指南

1. 项目概述&#xff1a;数学建模的“兵器库”与“作战手册” 刚接触数学建模的朋友&#xff0c;常常会有一个困惑&#xff1a;面对一个全新的问题&#xff0c;我该从哪里入手&#xff1f;用什么样的“武器”去解决它&#xff1f;这个“武器”&#xff0c;就是我们常说的数学模…

作者头像 李华
网站建设 2026/8/24 11:49:06

本地化AI双语PDF翻译工具:从部署到实战的完整指南

这次我们来看一个专门解决科研文献阅读痛点的开源工具——AI双语PDF翻译神器。对于经常需要阅读英文论文、技术文档的科研人员和开发者来说&#xff0c;跨语言阅读是最大的障碍之一。传统的PDF翻译工具要么效果差、格式混乱&#xff0c;要么收费昂贵、有隐私风险。这个开源项目…

作者头像 李华
网站建设 2026/8/24 11:48:13

Mindustry零门槛安装教程:从零跑通自动化塔防RTS的完整指南

Mindustry零门槛安装教程&#xff1a;从零跑通自动化塔防RTS的完整指南 【免费下载链接】Mindustry The automation tower defense RTS 项目地址: https://gitcode.com/GitHub_Trending/min/Mindustry Mindustry 是一款开源的自动化塔防 RTS&#xff08;即时战略&#x…

作者头像 李华