Vue3源码之proxyRefs

825 阅读2分钟

背景

最近在学习Vue3源码ref模块的的时候,一直好奇为什么ref在js里面需要通过.value去取值赋值。但是在模板中不需要。

然后深入发现在Vue源码中找到了这么一个API。就是今天要讨论的proxyRefs。

proxyRefs主要是在setup中的return中对返回结果做了处理。所以我们在模板语法中可以不需要使用.value。

源码中是如何实现的

我们看下Vue3源码中的handleSetupResult。代码目录为src/components。这一段代码就是对setup中我们return出去的一些变量或者方法的处理。其中还包含漏写return的时候的警告。setup() should return an object.

export function handleSetupResult(
  instance: ComponentInternalInstance,
  setupResult: unknown,
  isSSR: boolean
) {
  if (isFunction(setupResult)) {
    // setup returned an inline render function
    if (__SSR__ && (instance.type as ComponentOptions).__ssrInlineRender) {
      // when the function's name is `ssrRender` (compiled by SFC inline mode),
      // set it as ssrRender instead.
      instance.ssrRender = setupResult
    } else {
      instance.render = setupResult as InternalRenderFunction
    }
  } else if (isObject(setupResult)) {
    if (__DEV__ && isVNode(setupResult)) {
      warn(
        `setup() should not return VNodes directly - ` +
          `return a render function instead.`
      )
    }
    // setup returned bindings.
    // assuming a render function compiled from template is present.
    if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
      instance.devtoolsRawSetupState = setupResult
    }
    instance.setupState = proxyRefs(setupResult)
    if (__DEV__) {
      exposeSetupStateOnRenderContext(instance)
    }
  } else if (__DEV__ && setupResult !== undefined) {
    warn(
      `setup() should return an object. Received: ${
        setupResult === null ? 'null' : typeof setupResult
      }`
    )
  }
  finishComponentSetup(instance, isSSR)
}

这句代码就是使用proxyRefs对setupResult做处理。其他部分的代码不在这次的分析范围内~

instance.setupState = proxyRefs(setupResult)

继续沿着往下看,就到了proxyRefs的定义。是在reactivity/ref中定义实现的。我给参数加上一些注解方便大家理解。

  • 如果objectWithRefs是一个reactive,则直接返回,因为reactive的值是直接可以get取到值的。
  • 如果不是则通过shallowUnwrapHandlers对objectWithRefs做处理。
// objectWithRefs 就是返回的object钟可能包含ref类型的值
export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
  // 如果objectWithRefs是一个reactive,则直接返回,因为reactive的值是直接可以get取到值的。
  // 如果不是则通过shallowUnwrapHandlers对objectWithRefs做处理。
  return isReactive(objectWithRefs)
    ? objectWithRefs
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

那么关键部分来了,这个handlers是如何对objectWithRefs做的处理让我们在模板中不用.value就能取到值呢?

const shallowUnwrapHandlers: ProxyHandler<any> = {
  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)
    }
  }
}

首先get部分很容易理解。 直接对返回值做了unref处理,这样拿到的值就是ref处理前的值,也就不需要通过.value取了。

set部分。

  • 如果新不是ref类型,那么直接改掉oldValue(ref)的value。
  • 如果不是则直接替换成新的值返回。

这样就能保证在模板语法中使用ref类型的值不需要使用.value了。