shallowRef 用于历史栈管理,因其仅在整栈替换时触发更新,避免深度代理开销;不用 ref 或 reactive 是因快照含大量复杂结构,会导致初始化卡顿、内存暴涨和误更新。

在图形编辑器中用 shallowRef 管理历史操作栈,核心不是靠它自动响应嵌套变化,而是把它当作一个轻量容器,只在“整栈替换”时触发更新——这既避开深度代理开销,又精准控制视图刷新时机。
为什么不用 ref 或 reactive 存历史栈
历史栈通常是一组快照数组,比如:[state1, state2, state3]。如果用 ref 包裹,Vue 会对每个快照对象递归代理;而图形编辑器的快照往往包含大量 DOM 引用、画布坐标、图层树等复杂结构,深度代理会导致:
- 初始化卡顿:加载几十个快照时主线程明显延迟
- 内存暴涨:每个快照内部属性都被创建依赖追踪对象
- 误触发更新:编辑器内部状态微调(如鼠标 hover)意外触发快照比对
用 shallowRef 存栈 + 手动替换实现高效回溯
历史栈本身只需在用户执行 明确操作(如点击撤销、完成拖拽、确认合并图层)后整体切换,不需要监听内部字段变化。这时 shallowRef 是理想选择:
-
const undoStack = shallowRef([])—— 栈数组本身不被深度代理 - 每次存档:生成新快照(用
toRaw+ 深克隆脱敏),再整体赋值:undoStack.value = [...undoStack.value, newSnapshot] - 每次撤销:直接替换整个栈和当前状态:
currentState.value = undoStack.value.pop(),Vue 自动响应
配合 triggerRef 实现批量修改后统一刷新
某些场景下,你可能需要在不替换整个栈的前提下更新某几个快照的元信息(如标记“已同步”“含未保存更改”)。这时可:
立即学习“前端免费学习笔记(深入)”;
- 直接修改
undoStack.value[i].meta.isDirty = true(不会触发响应式) - 所有修改完成后调用
triggerRef(undoStack),强制视图更新一次 - 避免每改一个快照都触发渲染,减少冗余更新
关键注意事项
快照内容必须是普通 JS 数据,不能带响应式包装:
- 存档前用
toRaw(currentState.value)剥离响应式代理 - 再用
structuredClone或JSON.parse(JSON.stringify())(注意兼容性)深克隆 - 禁止把
reactive或ref对象直接 push 进栈,否则历史会相互污染


















