vue3响应式数据转回原始普通对象

在 Vue(尤其 Vue3)里,把响应式数据(Proxy/Ref)转回原始普通对象 (用 toRaw),核心是:绕开响应式代理、避免不必要更新、兼容外部系统、提升性能、防止异常


一、先搞懂:响应式 vs 原始数据

  • 响应式数据:reactive/ref 生成的 Proxy 代理对象,自带 getter/setter 拦截,用于依赖收集与视图自动更新。
  • 原始数据:被代理前的普通对象/数组/值,无 Proxy 包裹,修改不会触发 Vue 响应式更新。

二、为什么要转成原始数据(核心场景)

1. 与第三方库/原生 API 兼容(最常见)

很多库/API 不认识 Vue 的 Proxy,直接传会报错、序列化异常或行为异常。

  • JSON 序列化/存储JSON.stringify 对 Proxy 可能输出异常(含内部元数据),必须转原始

    js 复制代码
    const user = reactive({ name: '张三' })
    // ❌ 可能输出带 __v_isReactive 等内部字段
    localStorage.setItem('user', JSON.stringify(user))
    // ✅ 正确:转原始再序列化
    localStorage.setItem('user', JSON.stringify(toRaw(user)))
  • 图表/地图/Three.js/D3 等外部库 :它们期望纯 JS 对象,传 Proxy 会报错或性能极差

    js 复制代码
    import * as d3 from 'd3'
    const data = reactive([1,2,3])
    d3.select('svg').selectAll('rect').data(toRaw(data)) // ✅ 必须原始
  • Lodash/工具函数:深层遍历/克隆时,Proxy 会触发额外依赖追踪,导致性能问题或死循环

2. 性能优化:批量/大量数据操作

超大数组/对象做循环、排序、过滤、批量修改时:

  • 直接操作响应式对象:每一次修改都会触发依赖收集与可能的更新,性能极低

  • 转原始后操作:无 Proxy 开销、不触发更新 ,最后再同步回响应式对象

    js 复制代码
    const list = reactive(Array(10000).fill(0))
    const rawList = toRaw(list)
    // 批量处理:只操作原始,不触发更新
    for (let i = 0; i < rawList.length; i++) rawList[i] = i * 2
    // 最后同步一次(可选)
    list.splice(0, list.length, ...rawList)
3. 避免无限递归/循环更新
  • 场景1:对象自引用 (如树、链表、状态里存自身)

    js 复制代码
    const state = reactive({ data: null })
    state.data = toRaw(state) // ✅ 存原始,避免 Proxy 递归监听自己
  • 场景2:监听/计算属性里修改自身,容易死循环;用原始数据做中间计算

4. 只读不触发响应式(调试/日志/临时计算)
  • 打印/调试时,不想触发依赖追踪,也不想看到 Proxy(Object),想看真实数据

    js 复制代码
    const form = reactive({ name: '李四' })
    console.log(toRaw(form)) // { name: '李四' },干净的原始对象
  • 临时计算、判断、缓存,不需要视图更新,用原始更安全高效

5. 提交后端/接口请求
  • 后端只认普通 JSON,不认 Proxy;虽然多数 axios/fetch 能自动处理,但转原始更稳妥、避免隐式问题

    js 复制代码
    const params = reactive({ id: 1, name: 'xxx' })
    axios.post('/api', toRaw(params)) // ✅ 更干净
6. 规避响应式副作用
  • 某些场景下,你明确不希望修改触发视图更新(如临时草稿、后台计算),用原始数据操作

三、怎么转:Vue3 核心 API

js 复制代码
import { reactive, ref, toRaw } from 'vue'

// 1. reactive 对象 → 原始
const user = reactive({ name: '张三' })
const rawUser = toRaw(user) // 普通对象

// 2. ref 对象 → 原始(先取 .value)
const count = ref(0)
const rawCount = toRaw(count.value) // 原始值

// 3. 只读/浅响应 → 也能转
const ro = readonly(user)
toRaw(ro) === rawUser // true

四、重要注意

  1. toRaw 只解最外层:嵌套对象如果也是响应式,依然是 Proxy,需要递归处理
  2. 修改原始会同步到响应式 :原始与响应式指向同一块内存,改原始会改数据,但不触发更新
  3. 不要替换响应式对象user = rawUser 会丢失响应式,应修改属性或用 Object.assign
  4. Vue2 不同 :Vue2 用 Object.defineProperty,无 toRaw,一般用 JSON.parse(JSON.stringify(obj)) 深拷贝转普通对象

五、一句话总结

需要转原始数据,本质是:当你要和 Vue 响应式系统"脱钩"时 ------和外部交互、大量计算、避免递归、只看不更、性能敏感,就用 toRaw 拿原始数据。