Vue3 核心 API 完结篇:toRaw / markRaw / shallowReactive / shallowRef 等进阶响应式 API 详解

🔥 本文为「Vue3 核心与进阶」专栏系列文章,本专栏持续更新 Vue3 核心原理、工程化实践与面试高频考点:

  1. Vue3 核心 API 深度解析:ref / reactive / computed / watch
  2. Vue3 核心 API 补充解析:toRef / toRefs / unref / isRef
  3. 👉 本文: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 浅层次只读响应式 只读状态、配置对象

核心原则

  1. 默认用 reactive/ref,满足大部分场景;
  2. 遇到性能瓶颈或特殊场景时,再考虑使用 shallowXXX/markRaw/toRaw
  3. 不要过度优化,避免代码复杂度上升。
相关推荐
bigcarp1 小时前
edge浏览器IE模式(Internet Explorer 兼容)-tplink摄像头需要
前端·edge
27669582921 小时前
悟空租车帮app最新登录算法
开发语言·前端·python·悟空app·租车帮·租车帮app·租车帮登录逆向
摇滚侠1 小时前
微信小程序是前端,也需要 Java 开发的后端服务
java·前端·微信小程序
lxf_gis1 小时前
【JavaEE】Spring Web MVC
前端·spring·java-ee
sunxunyong2 小时前
集群增加用户&权限
前端·javascript·vue.js
吃西瓜的年年2 小时前
react(一)
前端·react.js·前端框架
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day6】
开发语言·前端·网络·数据库·c++·蓝桥杯
console.log('npc')2 小时前
2026前端进阶学习路线
前端·学习
每天都要进步哦2 小时前
React入门和快速上手
前端·vue.js·react.js·react