← 返回题目列表

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

困难 第 23 / 27 题 更新于 2026/07/29
VueshallowRefmarkRawtoRaw响应式

简化版

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 都不是日常状态的首选。 普通表单、列表、页面状态仍用 refreactive 更直观。

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

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

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

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

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

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

七、常见误区与追问

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

八、加强记忆

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