当前位置: 首页 > news >正文

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),仅供参考

http://www.cnnetsun.cn/news/4193240.html

相关文章:

  • 网站合规必备:legal-templates使用条款与Cookie声明怎么搭配用
  • 工业上位机通讯协议入门:从零理解Modbus到代码实现
  • 数学建模国赛C题实战:随机动态规划在供应链优化中的应用
  • 数学建模核心模型解析:从线性回归到动态规划的实战指南
  • 本地化AI双语PDF翻译工具:从部署到实战的完整指南
  • Mindustry零门槛安装教程:从零跑通自动化塔防RTS的完整指南
  • Windows截图全攻略:从系统快捷键到专业工具Snipaste
  • Notepad-- 跨平台文本编辑器:Windows、Linux、macOS 体验一致
  • 数学建模中的拟合:从最小二乘法到正则化,掌握模型优化的核心方法
  • 机器学习入门:从李宏毅课程到实战项目全流程指南
  • Zotero Attanger 附件管理完整指南:自动重命名、匹配与移动文献 PDF
  • 国内AI低代码,正悄悄改写制造业的规则
  • YOLO-World训练数据准备:从COCO到Grounding格式的完整转换指南
  • AI编程助手工程化实践:从效率工具到稳定生产力的安全集成指南
  • C++万能引用与引用折叠:从右值引用到完美转发的核心机制解析
  • 从高斯牛顿法到视觉SLAM:非线性最小二乘优化的原理与实践
  • 为什么 ComfyUI 的开源社区能让节点扩展贡献变得这么省事?
  • 让任意Python脚本可复现运行:Uv2nix development-scripts模式
  • C++模板编程:类模板与模板类的本质区别与实战应用
  • Kaggle竞赛零基础实战指南:从入门到简历项目全流程
  • 重构祖传代码:从面条式代码到清晰领域模型的实战指南
  • RustDesk 私有化高可用部署:双信令节点加四层负载均衡落地
  • AI自动化代理入门:从核心原理到LangChain实战构建智能业务助手
  • STM32+FreeRTOS信号量原理与实战:从内存布局到三类选型
  • C++模板核心概念解析:类模板与模板类的本质区别
  • Arnis 三步把真实城市搬进《我的世界》:免费开源的地理地图生成工具
  • 5 步跑通 Deep-Live-Cam:从空白环境到实时换脸的完整路线图
  • C++模板核心解析:类模板与模板类的本质区别与应用实践
  • mpv 图片轮播:一条命令把文件夹挂上展厅墙
  • 平台视觉API接入实战:从环境配置到批量处理全流程指南