Skip to content

我们知道 vue3 中 template中使用 ref类型时候,不需要使用.value访问,vue 会为我们自动脱ref,那么在源码层面,这是怎么实现的呢?

刚开始用到这个特性的时候,最自然的想法这是 vue 在模板编译的时候,帮我们自动加上了 .value,但查看源码,会让我们大跌眼镜,

实际上是在 setup 返回 ref 响应式数据的时候,vue 用proxyRefs函数对返回的数据又"包了一层",就像使用了一个代理对象,代理了对 ref 响应式数据的访问和设置。

接下来我们来看下源码(版本 3.2.3),

proxyRef 函数 在 vue/core 源码下 reactivity 子包下的 refs.ts 中:

typescript
const shallowUnwrapHandlers: ProxyHandler<any> = {
  // 调用unref
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    } else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}

export function proxyRefs<T extends object>(
  objectWithRefs: T,
): ShallowUnwrapRef<T> {
  // reactive类型直接返回,否则用返回一个proxy
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

在 runtime-core 包下的 componet.ts 中,调用了 proxyRefs 方法构造 setup 的返回体:

typescript
export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean,
): void {
  if (isFunction(setupResult)) {
    // .....
  } else if (isObject(setupResult)) {
    if (__DEV__ && isVNode(setupResult)) {
    /// .... 
    instance.setupState = proxyRefs(setupResult)
    // ....
  }
  finishComponentSetup(instance, isSSR)
}

可以看到我们在 setup 中返回的返回体,最终会被proxyRefs函数处理后,返会给模板中使用,比如:

javascript

export default{
  setup(){
    return{
      a: ref(1)
    }
  }
}

最终再模板中使用的其实是proxyRefs函数处理后的代理对象:

javascript
proxyRefs(
  {
    a: ref(1)
  }
)

观察proxyRefs源码,我们可以看到它是进行了一次浅代理,即只进行第一层对象的代理,