全量快照导致内存压力,因其每次操作均用structuredclone保存完整state,内存占用随操作次数线性增长;如10万节点编辑器单次快照数mb,100次即达数百mb,易引发gc频繁、卡顿或oom。

为什么全量快照会带来内存压力
响应式系统(如 Vue3)本身不记录历史,撤销重做需额外保存状态。若每次操作都用 structuredClone(toRaw(state)) 保存完整对象,内存占用随操作次数线性增长。一个含 10 万个节点的富文本编辑器,单次快照可能达数 MB;连续 100 次操作,就可能吃掉数百 MB 内存——尤其在低端设备或长时间编辑场景下,极易触发 GC 频繁、卡顿甚至 OOM。
增量补丁(Delta)的核心思路
不存“整个世界”,只记“改了哪一行”。把每次用户操作抽象为一条不可变的变更指令,例如:
- INSERT:位置 127,插入字符串 "Vue3"
- DELETE:位置 89,长度 5
- REPLACE:位置 200,旧值 "data" → 新值 "state"
每条补丁体积极小(通常几十到几百字节),且天然支持合并:连续输入可聚合成一个 INSERT 补丁,而非 10 个单字符快照。
如何在响应式系统中落地 Delta 方案
关键不在替换响应式机制,而在分层解耦:视图层仍用 reactive 管理实时状态,历史层用轻量 Delta 栈管理变更逻辑。
- 定义纯数据补丁类型:
type Delta = { op: 'insert' | 'delete' | 'replace'; pos: number; data?: string; oldData?: string } - 操作结束时生成并压栈 Delta,而非克隆整个 state
- 执行 undo 时,从栈顶取 Delta,反向应用(如 INSERT → 对应 DELETE)还原 state
- state 本身仍是 reactive 对象,所有变更通过 直接赋值或 proxy setter 触发响应式更新,无需重建整个 reactive 实例
兼顾可靠与性能的实用策略
纯 Delta 虽省内存,但恢复逻辑复杂;全量快照虽简单,却扛不住高频编辑。生产环境推荐混合策略:
- 自动快照锚点:每 N 次 Delta 合并后(如 20 步),或检测到大范围操作(如粘贴整段 Markdown),主动存一次全量快照作为“恢复基点”
- 栈长度硬限制 + LRU 裁剪:undoStack 最多保留 50 条 Delta;超出时移除最老条目,但保留最近一次快照,确保任意时刻最多只需回溯 50 步 + 1 次全量加载
- 按区域隔离 Delta:对编辑器内嵌的代码块、表格、图片等独立子区域分别维护 Delta 栈,避免单区域高频操作污染全局历史









