vue3 模板脱ref的原理

211 阅读2分钟

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

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

实际上是在 setup 返回 ref 响应式数据的时候,vue 用proxyRefs函数对返回的数据进行处理,proxyRefs函数的作用就像是给数据加了一层代理,使得在访问和设置这些ref数据时,能够自动脱去.value

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

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

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 中,handleSetupResult函数会调用proxyRefs方法来处理setup的返回值:

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中返回一个对象时,例如:


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

实际上,最终在 template 中使用的对象是经过proxyRefs处理后的代理对象:

proxyRefs(
  {
    a: ref(1)
  }
)

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

也就是说,对于具有多层对象的嵌套 ref 来说,我们还是要加上.value,比如

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

我们再模板中使用的时候,需要写成a.b.value

总结: Vue3模板中refvalue的实现原理是通过proxyRefs函数对setup返回的对象进行代理,从而使得大多数情况下我们无需手动添加.value即可访问ref的值。但要注意,这只是浅层代理,对于嵌套的ref,我们仍需手动处理。