2025.09.26

Vue3 响应式工具:toRef、toRefs、unRef、proxyRef

在使用 Vue 3 的 reactive 时,我们经常习惯性的使用 ES6 解构赋值。

在使用 Vue 3reactive 时,我们经常习惯性的使用 ES6 解构赋值

const state = reactive({
  name: '张三',
  age: 18
})

let { name } = state

effect(() => {
  console.log(name) // 仅仅是一个普通的字符串 "张三"
})

// 1秒后修改,effect 不会重新触发
setTimeout(() => {
  name = '李四'
}, 1000)

这样的话会发现丢失了响应式, 💡 reactive 返回的是一个 proxy 对象,而结构出来的 name 只是一个基础类型, 失去了与原对象的代理联系

1.toRef

为了解决这个问题, 通常的情况下会使用 toRef, 让结构出来的变量可以触发响应式更新

const state = reactive({
  name: '张三',
  age: 18
})

const name = toRef(state, 'name') 
console.log(name)
effect(() => {
  console.log(name.value) })

setTimeout(() => {
  state.name = '李四'}, 1000)

这时候输出的 name 的类型

可以看到和 RefImpl 的类型不同, 它是一个特质的 ObjectRefImpl 类, 并且多了属性 _object_key,这两个属性分别指向了 statename

核心实现

这个 toRef 从使用上可以看到接受一个对象和 key 所以⬇️

function toRef(object: object, key: string | symbol) {
  return {
    get value() {
      return object[key]
    },
    set value(newValue) {
      object[key] = newValue
    }
  }
}

这样的话其实就可以更新了, 但是官方的实现是一个类,所以修改一下

// ref.ts
class ObjectRefImpl {
  [ReactiveFlags.IS_REF] = true

  constructor(
    public _object,
    public _key
  ) {
  }

  get value() {
    return this._object[this._key]
  }

  set value(newValue) {
    this._object[this._key] = newValue
  }
}

export function toRef(target: object, key: string | symbol) {
  return new ObjectRefImpl(target, key)
}

2. toRefs

const state = reactive({ name: '张三', age: 18 })

const { name, age } = toRefs(state)

setTimeout(() => {
  name.value = '李四'
}, 1000)

有了 toRef,toRefs 不就轻松实现嘛 既然 toRef 可以将对象的一个属性转为响应式的引用, 那 toRefs的不就能轻松实现嘛

遍历所有属性, 调用 toRef

export function toRefs(object) {
  const ret = {}
  for (const key in object) {
    ret[key] = toRef(object, key)
  }
  return ret
}

这样的话就导出都是 .value了,每次 .value 也挺烦的

这样的话就引出来两个辅助方法了

3. unRef、proxyRef

什么是 proxyRef

为什么在 template 中使用 ref 不需要 .value 这其实就是 proxyRef 的作用

当访问这个代理的属性时,会自动解包.valuereactive 很像, 不直接使用 reactive的原因是 reactive 是深层响应式的, proxyRef 是浅层的

export function proxyRefs(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      // return isRef(res) ? res.value : res      return unRef(value)    },
    set(target, key, newValue, receiver) {
      const oldValue = target[key]
      if (isRef(oldValue) && !isRef(newValue)) {
        oldValue.value = newValue
        return true
      }

      return Reflect.set(target, key, newValue, receiver)
    }
  })
}

export function unRef(value) {
  return isRef(value) ? value.value : value
}