用Map实现轻量级撤销重做状态机的关键是精准捕获快照时机与原子化还原:通过Map管理past、present、future三段状态,仅在用户显式操作或输入确认时存深拷贝快照,undo/redo通过指针移动与数组弹出实现,需手动限制历史深度并避免大对象全量存储。

用 Map 实现轻量级撤销重做状态机,关键不在于堆栈有多复杂,而在于状态快照的捕获时机和还原方式是否原子、可逆。它适合节点少、操作频次低、不依赖外部库的场景,比如表单编辑器、简易流程图或本地笔记工具。
用 Map 存储“过去-当前-未来”三段式状态
Map 本身不带历史能力,但可以模拟 Redux Undo 的结构:用一个 Map 实例统一管理三个逻辑区——past(数组)、present(当前状态对象)、future(数组)。例如:
- past:存储每次变更前的完整状态快照(建议深拷贝或序列化)
- present:始终指向最新有效状态,所有读写操作都基于它
- future:仅在执行 undo 后填充,执行 redo 时从中取值并移入 past
触发快照的两个黄金时机
不是每次 setState 都存,而是只在用户明确意图操作时捕获快照,避免冗余。典型时机包括:
- 用户点击“保存”“添加节点”“删除连线”等显式动作后,立即调用
saveSnapshot() - 编辑字段失焦(
onBlur)或回车确认时,对输入内容做一次快照 - 避免在拖拽过程中高频存——可在
dragend事件里统一存一次最终位置
撤销与重做的核心逻辑
操作本质是移动指针+交换数据,不涉及深层遍历:
-
undo:若
past.length > 0,将present推入future,再把past.pop()赋给present -
redo:若
future.length > 0,将present推入past,再把future.pop()赋给present - 每次操作后清空对方堆栈中“断连”的部分(如 undo 后新操作,就清空 future)
内存与性能的小提醒
Map 本身不自动限制容量,需手动控制历史深度:
- 在
saveSnapshot()中检查past.length,超出阈值(如 50)就past.shift() - 对大型对象(如含 base64 图片的节点),不要直接存整个对象,改存 diff 或只存变更字段
- 用
structuredClone()(现代浏览器)或JSON.parse(JSON.stringify())做浅层快照,避免引用污染

















