shallowRef的核心价值是精准控制更新边界,避免对大文档深度代理;它只在整份文档替换时触发响应,局部修改需手动triggerRef,配合协同协议降低内存与渲染压力。

在协同编辑场景中管理大文档(如万字富文本、多人实时协作的 Markdown 编辑器),shallowRef 的核心价值不是“让每个字符都响应”,而是**精准控制更新边界,避免对整篇文档做无意义的深度代理**。它配合协同协议(如 OT 或 CRDT)和手动触发机制,能显著降低内存压力与渲染抖动。
为什么不用 ref 管理大文档?
若用 ref({ content: longText, cursor: {}, selections: [...] }):
- Vue 会递归遍历整个
content字符串(虽字符串本身不可变,但其包装对象、嵌套 selection 对象仍被 Proxy 包裹) - 每次光标移动、选区变化、格式标记插入,都可能触发深层属性变更,引发不必要的依赖收集与副作用执行
- 多人并发操作时,频繁 patch 合并产生的临时文档快照若全走深度响应式,GC 压力陡增,页面易卡顿
shallowRef 的正确用法:只响应“整份文档替换”
声明方式与 ref 相同,但语义不同:
import { shallowRef, triggerRef } from 'vue'
// 文档数据本身保持原始结构,不被 Proxy 深度劫持
const doc = shallowRef({
id: 'doc-123',
content: '', // 大段纯文本或 AST 树(如 remark AST)
version: 0,
collaborators: []
})
-
整份更新才触发视图刷新:例如收到服务端广播的新快照:
doc.value = newSnapshot✅ -
局部修改不自动响应:比如本地输入一个字:
doc.value.content += 'a'❌ 页面不会重绘——这反而是优势,避免每键都触发 diff -
需要局部变更生效?手动触发:完成一段编辑后统一调用
triggerRef(doc)✅(比逐次响应更可控、更高效)
与协同逻辑的配合要点
协同编辑的核心是“状态收敛”,而非“实时响应”。shallowRef 正好契合这一模型:
从零开始编排 SpringBoot+Vue 全栈项目工作流,适用于新建全栈项目或完整脚手架请求,触发后依次调用 product‑spec‑builder(需求调研)等模块。
-
本地编辑暂存于非响应式结构:用普通对象/数组记录增量操作(如
localOps = [{ type: 'insert', pos: 100, text: 'hello' }]),不挂到doc.value上 -
合并完成后一次性赋值:OT 合并或 CRDT 计算出最终文档后,再执行
doc.value = mergedDoc -
光标与选区单独响应式管理:用普通
ref管理cursor和selections,它们体积小、变更频次高,适合深度响应 -
历史快照列表用 shallowRef:如
history = shallowRef([]),只在用户点击“撤回”时整体替换history.value = newHistoryList
补充:搭配 markRaw 防止意外代理
若文档内容含第三方富文本节点(如 Draft.js 的 ContentState、Slate 的 Node 对象),建议进一步包裹:
import { markRaw } from 'vue'
const doc = shallowRef({
content: markRaw(initialContentState), // 明确跳过响应式处理
metadata: { author: 'user-a' }
})
这样既保留对象原生行为(如方法调用、内部私有字段),又避免 Vue 试图 Proxy 其复杂原型链导致报错。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!









