proxy实现多级撤销/重做核心在于精准捕获状态变更粒度、保留不可变快照、隔离副作用;通过set陷阱生成新状态节点而非原地修改,分层管理快照(路径+值),undo/redo由状态驱动并保持一致性,冻结历史与隔离访问确保健壮性。

用 Proxy 实现多级撤销/重做,核心不在于“记录操作”,而在于**精准捕获状态变更的粒度、保留不可变快照、并隔离副作用**。它不是简单地 push/pop 历史数组,而是让每次合法的 set 操作都生成一个可回溯的新状态节点,同时保持原始数据不被污染。
1. 用 Proxy 拦截所有写入,触发快照生成
代理对象的 set 陷阱是入口。每次属性赋值前,不做原地修改,而是:
- 基于当前状态深拷贝(或结构共享)生成新状态对象
- 仅更新目标属性,其余保持引用不变(提升性能)
- 将新状态推入 undo 栈,并清空 redo 栈(因新操作打断重做链)
- 返回 true 表示赋值成功(符合 trap 规范)
注意:不能直接修改 target,否则快照失去意义;也不宜在 set 中同步执行 UI 更新——那是响应式层的事,模型层只管状态演进。
2. 快照管理需分层:操作级 + 状态级
单纯保存整个 state 对象快照内存开销大。更优做法是:
- 每个快照记录「变更路径」+「旧值」+「新值」,如
{ path: ['user', 'profile', 'name'], oldValue: 'Alice', newValue: 'Bob' } - 配合一个轻量 state tree(如 Immutable.js 或简易结构共享对象),让快照能高效还原
- 支持批量操作合并(如表单连续输入):用 group 标记包裹多次 set,只生成一个快照节点
这样 undo 不是“倒放操作”,而是“跳转到上一个一致状态”,逻辑更可靠,也避免中间非法状态暴露。
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
3. 撤销/重做行为由状态驱动,而非命令驱动
传统命令模式(Command Pattern)需为每类操作实现 execute / undo 方法,易膨胀。Proxy 方案更简洁:
-
undo():从 undo 栈弹出一个快照,用其完整状态替换当前代理的目标对象(即
target = snapshotState),再通知监听者 - redo():从 redo 栈弹出快照同理恢复
- 所有变更仍经由同一套 Proxy set 拦截,保证一致性 —— 即使重做,也走相同的校验、日志、路径记录逻辑
这种设计天然支持嵌套撤销(比如组件内局部 undo 不影响全局状态),只需为不同 scope 创建独立的 proxy 实例和栈。
4. 防止意外污染:冻结历史 & 隔离访问
历史快照一旦生成,必须不可变:
- 快照对象建议用
Object.freeze()或structuredClone()(若环境支持)生成真正不可变副本 - 代理的 get 陷阱可加防护:若访问的是内部管理字段(如
_history,_stackIndex),直接抛错或静默忽略 - 对外暴露的始终是 proxy 实例,永远不要把原始 target 或快照对象泄露出去
这样既保障了撤销系统的健壮性,也让调用方无需关心底层机制,就像操作普通对象一样自然。










