我们知道 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模板中ref脱value的实现原理是通过proxyRefs函数对setup返回的对象进行代理,从而使得大多数情况下我们无需手动添加.value即可访问ref的值。但要注意,这只是浅层代理,对于嵌套的ref,我们仍需手动处理。