FumadocsZDecode
Vue

ref 与 reactive

一、区别

// ref:包一层 { value: xxx },任意类型都能接
const count = ref(0)
const user = ref({ name: 'Tom' })
console.log(count.value) // 要 .value

// reactive:直接代理对象,只能接对象 / 数组
const state = reactive({ name: 'Tom' })
console.log(state.name) // 不用 .value
refreactive
接收类型任意只能对象 / 数组
访问方式.value直接访问
整体替换count.value = 新值state = 新对象 ❌ 丢失响应式
模板中自动解包,无需 .value直接用

二、ref 传入对象时,内部是 reactive

是的。 ref 内部会调用 toReactive(),对象类型直接走 reactive()

// 源码简化
function ref(rawValue) {
  return {
    get value() {
      track(this, 'value')
      return this._value
    },
    set value(newVal) {
      this._rawValue = newVal
      this._value = toReactive(newVal) // 关键在这里
      trigger(this, 'value')
    },
  }
}

function toReactive(value) {
  return isObject(value) ? reactive(value) : value
}

所以:

const data = ref({ nested: { a: 1 } })

// data         → ref 对象(有 .value)
// data.value   → reactive 代理对象
// data.value.nested → 也是 reactive(Proxy 的 get 惰性递归代理)

data.value.nested.a = 2 // ✅ 触发更新

验证

import { ref, isReactive } from 'vue'

const data = ref({ nested: { a: 1 } })

isReactive(data) // false,ref 本身不是 reactive
isReactive(data.value) // true,被 reactive() 包了
isReactive(data.value.nested) // true,Proxy 惰性递归代理

三、完整流程

ref({ nested: { a: 1 } })

  ├─ 创建 ref 对象 { _value, get value(), set value() }

  ├─ 传入的是对象 → toReactive() → reactive({ nested: { a: 1 } })
  │                                      │
  │                                      └─ 返回 Proxy

  └─ 访问 data.value.nested 时
       → Proxy get 拦截
       → nested 也是对象 → 惰性调用 reactive({ a: 1 })
       (不是初始化时递归,是访问时才代理)

四、什么时候用 ref,什么时候用 reactive

// 基本类型 → 只能 ref
const count = ref(0)

// 对象且不需要整体替换 → reactive 更方便(不用 .value)
const form = reactive({ name: '', age: 0 })

// 对象但需要整体替换 → ref
const user = ref({ name: 'Tom' })
user.value = { name: 'Jerry' } // ✅ ref 可以整体替换
// reactive 整体替换会丢失响应式

五、一句话总结

ref 传入对象时,内部调用 toReactive(),本质就是 reactive() 包一层。所以 ref({}).value 和深层属性都是 Proxy 代理的响应式对象,ref 只是多了 .value 的包装,让基本类型也能响应式。

On this page