🔥 本文为「Vue3 核心与进阶」专栏系列文章,本专栏持续更新 Vue3 核心原理、工程化实践与面试高频考点:
- Vue3 核心 API 深度解析:ref / reactive / computed / watch
- Vue3 核心 API 补充解析:toRef / toRefs / unref / isRef
- 👉 本文:Vue3 核心 API 完结篇:toRaw / markRaw / shallowReactive / shallowRef 等进阶响应式 API 详解
一、为什么需要这些"进阶响应式 API"?
Vue3 的默认响应式(reactive/ref)是深度监听的:对象嵌套越深、数据量越大,依赖收集和更新触发的开销就越高。
同时,有些场景我们不希望被 Vue 代理:
- 第三方库实例(如 Leaflet、ECharts、类的实例)
- 不需要响应式的纯数据缓存
- 性能敏感的大数据列表
这时候,toRaw/markRaw/shallowReactive/shallowRef 等 API 就派上用场了,它们能帮我们精准控制响应式范围,实现性能优化和特殊场景兼容。
二、toRaw:获取响应式对象的原始数据
1. 核心作用
toRaw(proxy) 用于获取 reactive/readonly 代理对象的原始普通对象,它不会触发依赖收集,也不会触发响应式更新。
2. 基本用法
typescript
import { reactive, toRaw } from 'vue'
const original = { name: 'Vue3' }
const proxy = reactive(original)
console.log(proxy === original) // false(proxy 是代理对象)
console.log(toRaw(proxy) === original) // true(拿到原始对象)
3. 典型使用场景
- 调试与日志:打印原始数据,避免代理对象干扰调试
- 避免不必要的响应式:读取数据时不触发依赖收集
- 与第三方库交互:传递原始对象给不兼容 Vue 代理的库
⚠️ 注意:
toRaw只对reactive/readonly生成的代理有效,对ref包装的基本类型无效;如果要获取ref的原始值,直接用.value即可。
三、markRaw:标记对象"永远不被代理"
1. 核心作用
markRaw(obj) 标记一个对象,让它永远不会被 Vue 转为响应式代理 ,即使被嵌套在 reactive 内部。
2. 基本用法
typescript
import { reactive, markRaw } from 'vue'
const rawObj = markRaw({ name: '不可代理' })
const state = reactive({
raw: rawObj,
normal: { name: '可代理' }
})
console.log(state.raw === rawObj) // true(未被代理)
console.log(state.normal !== rawObj) // true(被代理)
3. 典型使用场景
- 第三方库实例 :如
new Map()、new Set()、图表实例、地图实例 - 类实例:自定义类的实例,不需要响应式
- 性能优化:大数据对象、静态配置对象,避免不必要的代理开销
⚠️ 注意:
markRaw是不可撤销的,一旦标记,整个对象及其引用都不会被代理;不要标记 Vue 本身需要响应式的对象(如组件实例)。
四、shallowReactive:浅层次响应式
1. 核心作用
shallowReactive(obj) 创建一个仅监听根层级属性变化的响应式对象,嵌套对象不会被代理,保持原始引用。
2. 基本用法
typescript
import { shallowReactive, watch } from 'vue'
const state = shallowReactive({
foo: 1,
nested: { bar: 2 }
})
// 监听根层级:有效
watch(() => state.foo, (val) => console.log('foo changed:', val))
// 监听嵌套层级:无效
watch(() => state.nested.bar, (val) => console.log('bar changed:', val))
state.foo = 2 // 触发响应式更新
state.nested.bar = 3 // 不触发响应式更新(嵌套对象未被代理)
3. 典型使用场景
- 性能优化:大数据列表、树形结构,只需要监听根层级变化
- 静态嵌套数据:嵌套数据不需要响应式,只关心顶层状态
- 与 immutable 数据结合:根层级替换时触发更新,内部数据保持不变
⚠️ 注意:如果直接替换整个嵌套对象,会触发更新:
typescript
state.nested = { bar: 3 } // 触发响应式更新
五、shallowRef:浅层次 ref
1. 核心作用
shallowRef(value) 创建一个 ref,仅监听 .value 的赋值操作 ,不会对 .value 内部做深度响应式转换。
2. 基本用法
typescript
import { shallowRef, watch } from 'vue'
const state = shallowRef({ count: 0 })
// 监听 .value 赋值:有效
watch(state, (val) => console.log('state changed:', val), { deep: true })
// 监听内部属性:无效
watch(() => state.value.count, (val) => console.log('count changed:', val))
state.value.count = 1 // 不触发更新
state.value = { count: 2 } // 触发更新
3. 典型使用场景
- 大对象/复杂数据:如富文本编辑器内容、Canvas 画布状态
- 第三方库状态:不需要深度监听的库实例
- 性能敏感场景:避免深度遍历带来的性能开销
⚠️ 对比
shallowReactive:
shallowReactive:对象本身是响应式,但只监听根层级shallowRef:.value是响应式,但内部值不做深度代理
六、其他进阶响应式 API 补充
1. shallowReadonly
- 作用:创建一个浅层次只读的响应式对象,根层级属性不可修改,嵌套对象可修改且无响应式。
- 用法:
typescript
import { shallowReadonly } from 'vue'
const state = shallowReadonly({ foo: 1, nested: { bar: 2 } })
state.foo = 2 // 报错(只读)
state.nested.bar = 3 // 可修改,但无响应式
2. isReactive / isReadonly / isProxy
- 作用:判断对象是否为响应式/只读/代理对象。
- 用法:
typescript
import { reactive, readonly, isReactive, isReadonly, isProxy } from 'vue'
const proxy = reactive({})
const ro = readonly(proxy)
console.log(isReactive(proxy)) // true
console.log(isReadonly(ro)) // true
console.log(isProxy(proxy)) // true
七、实战场景:性能优化最佳实践
场景 1:大数据列表渲染
typescript
import { shallowRef, markRaw } from 'vue'
// 假设这是 10000 条数据的列表
const bigList = shallowRef(
markRaw(Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `item-${i}` })))
)
// 只在整体替换时触发更新,避免逐条修改的性能开销
function refreshList(newList) {
bigList.value = markRaw(newList)
}
场景 2:第三方库实例管理
typescript
import { ref, markRaw } from 'vue'
import * as echarts from 'echarts'
// 标记 echarts 实例,避免被 Vue 代理
const chartInstance = ref(markRaw(echarts.init(document.getElementById('chart'))))
// 后续操作实例时,不会触发不必要的响应式
chartInstance.value.setOption({ /* ... */ })
八、总结与选型建议
| API | 作用 | 适用场景 |
|---|---|---|
toRaw |
获取代理对象的原始数据 | 调试、与第三方库交互、避免依赖收集 |
markRaw |
标记对象永远不被代理 | 第三方库实例、类实例、静态大数据 |
shallowReactive |
浅层次响应式,仅监听根层级 | 大数据列表、静态嵌套数据、性能优化 |
shallowRef |
浅层次 ref,仅监听 .value 赋值 |
大对象、复杂状态、第三方库状态管理 |
shallowReadonly |
浅层次只读响应式 | 只读状态、配置对象 |
✅ 核心原则:
- 默认用
reactive/ref,满足大部分场景; - 遇到性能瓶颈或特殊场景时,再考虑使用
shallowXXX/markRaw/toRaw; - 不要过度优化,避免代码复杂度上升。