← 返回题目列表

shallowRef、markRaw 和 toRaw 分别适合什么场景?

困难 第 23 / 27 题 更新于 2026/07/29
VueshallowRefmarkRawtoRaw响应式
学习 AI 实战项目

简化版

shallowRef 只追踪 .value 替换,不深层代理内部对象;markRaw 标记对象不被 Vue 转成响应式;toRaw 从响应式代理取回原始对象。它们都用于控制响应式成本和边界,常见于大对象、第三方实例和性能敏感场景。

详细版

普通 ref 包含对象时,内部对象会被深层响应式处理。 当对象很大、来自第三方库、或不希望被代理时,可以使用浅层或原始对象能力。

const chart = shallowRef(null)
chart.value = createChart(dom)

const rawMap = markRaw(new Map())

const original = toRaw(state)

shallowRef 适合“只关心整体替换”;markRaw 适合“永远不要代理这个对象”;toRaw 适合“临时拿原始对象做比较、传给第三方库或调试”。不要把它们当作绕过 Vue 更新机制的常规手段。

完整版教学

一、Vue 深层响应式有收益,也有成本

Vue 的响应式会代理对象属性,让读取和修改都能被追踪。 这对普通业务状态很方便。 但如果对象非常大,或对象本身由第三方库管理,深层代理就可能没有收益甚至带来问题。

const state = reactive({
  user: { name: 'Tom' },
  list: [{ id: 1 }, { id: 2 }]
})

假设一个图表实例内部有 500 个字段、几十个方法和循环引用。 把它深层响应式化没有业务价值,因为页面并不依赖这些内部字段渲染。 这时应控制响应式边界。

二、shallowRef 只关心 value 这一层

shallowRef 会追踪 .value 的替换,但不会把内部对象深层转成响应式。 当你只需要“对象换了就更新”,不需要追踪对象内部字段时,它很合适。

const chart = shallowRef(null)

onMounted(() => {
  chart.value = createChart(container.value)
})

function resize() {
  chart.value?.resize()
}
操作shallowRef 是否触发依赖原因
chart.value = newChart是.value 被替换
chart.value.resize()否内部方法调用不追踪
chart.value.options.title = 'A'否深层属性不响应

如果需要更新视图,应该替换 .value,或者在必要时手动触发。 不要期待深层字段变更能自动刷新。

三、markRaw 是明确告诉 Vue 不要代理

markRaw(obj) 会给对象打标记,使它不会被转换成响应式代理。 它适合第三方类实例、不可变大对象、复杂库对象等。 例如 ECharts、地图实例、编辑器实例通常不应该被 Vue 深代理。

import { markRaw } from 'vue'

const editor = markRaw(createEditor({
  theme: 'dark'
}))
第三方实例
├─ 内部状态由库维护
├─ 方法依赖 this 或私有字段
└─ Vue 深代理可能无收益或有风险

如果一个页面创建 3 个编辑器实例,每个实例内部字段很多,markRaw 能减少不必要的代理和追踪。 它也是在表达所有权:这个对象归第三方库管理,不归 Vue 响应式系统管理。

四、toRaw 是临时拿回原始对象

reactive 返回的是代理对象。 有时你需要拿到原始对象,比如和第三方库做严格引用比较、调试、序列化前处理。 toRaw(proxy) 可以返回被代理前的对象。

const raw = { id: 1 }
const state = reactive(raw)

console.log(state === raw) // false
console.log(toRaw(state) === raw) // true

但 toRaw 不应该长期保存并用来绕过响应式更新。 如果你拿着 raw 对象到处改,Vue 可能追踪不到变化。 它更像临时查看原件,而不是新的状态管理入口。

五、三者的选择看你要控制哪条边界

shallowRef 控制“只追踪外壳替换”。 markRaw 控制“这个对象不要被代理”。 toRaw 控制“从代理临时回到原始对象”。

API控制点典型用途风险
shallowRef只追踪 value图表实例、组件实例、大对象整体替换深层变更不更新
markRaw禁止代理对象第三方库实例、不可变对象内部变化不可响应
toRaw取原始对象比较、调试、传给库长期持有会绕开追踪

这三个 API 都不是日常状态的首选。 普通表单、列表、页面状态仍用 ref、reactive 更直观。

六、性能优化要有具体收益

不要看到大对象就条件反射使用浅层响应式。 如果模板确实依赖对象内部字段,浅层处理会让更新变得麻烦。 如果对象内部字段永远不参与渲染,深层响应式才是浪费。

需要深层响应:
form.user.name 改了页面要更新

适合浅层或 raw:
chartInstance.resize() 由库自己处理

记忆钩子:shallowRef 看盒子换没换,markRaw 贴免检标签,toRaw 临时看原件。

面试中可以用“图表实例”作为例子。 这个例子能同时说明第三方对象、不需要深层代理、只关心生命周期和整体引用。

七、常见误区与追问

  • 误区:shallowRef 里的对象内部变化也会自动更新视图。 它只追踪 .value 层,深层属性变化不会自动触发依赖。
  • 误区:markRaw 能提升所有对象性能。 只有不需要响应式代理的对象才适合标记,普通业务状态不应乱用。
  • 误区:toRaw 可以长期当状态来改。 长期持有 raw 并修改会绕过 Vue 追踪,容易造成视图不同步。
  • 追问:第三方图表实例为什么适合 markRaw? 其内部状态由库维护,Vue 深层代理没有渲染收益,还可能影响方法和性能。
  • 追问:shallowRef 和 ref 最大区别是什么? 对对象值,ref 会深层响应式处理,shallowRef 只追踪 .value 替换。
  • 追问:什么时候仍应使用普通 reactive? 模板和计算逻辑需要追踪对象内部字段时,应使用普通响应式。

八、加强记忆

这题按“深层代理成本”切入。Vue 默认深层响应式适合业务状态;第三方实例、大对象和只看整体替换时才考虑 shallowRef、markRaw、toRaw。回答时把三者分别定位成看盒子、免代理、取原件,再补不要绕开响应式长期改 raw。