toRefs函数解决了响应丢失的问题,同时也带来新的问题。由于toRefs会把响应式数据的第一层属性值转换为ref,因此必须通过value属性访问值,这样增加了用户的心智负担,因为通常情况下用户是在模板中访问数据的,用户肯定不希望编写下面这样的代码
<p>{{foo.value}} / {{bar.value}}</p>
因此需要有自动脱ref的能力,自动脱ref指的是如果读取的属性是一个ref,则直接将该ref对应的value属性值返回,也就是无须通过newObj.foo.value来访问值,只需要通过newObj.foo就能返回值。
要实现这个功能,需要使用Proxy为newObj创建一个代理对象,通过代理来实现,如下面代码所示:
function proxyRefs(target){
return new Proxy(target,{
get(target,key,receiver){
const value = Reflect(target,key,receiver)
// 自动脱ref实现,如果读取的值是ref,则返回它的value属性值
return value._v_isRef?value.value:value
}
})
}
// 调用proxyRefs函数创建代理
const newObj = proxyRefs({...toRefs(obj)})
这样就实现了自动脱ref:
console.log(newObj.foo) //1
console.log(newObj.bar) //2
实际上,在编写Vue.js组件时,组件中的setup函数返回的数据会传递给proxyRefs函数进行处理:
const MyComponent = {
setup(){
const count = ref(0)
// 返回的这个对象会传递给proxyRefs
return {count}
}
}
相应的,设置属性的值也应该有自动为ref设置值的能力,实现起来也很简单,只需要添加对应的set拦截好桉树即可:
function proxyRefs(target){
return new Proxy(target,{
get(target,key,receiver){
const value = Reflect(target,key,receiver)
// 自动脱ref实现,如果读取的值是ref,则返回它的value属性值
return value._v_isRef?value.value:value
}
set(target, key, newValue, receiver){
// 通过target读取真实值
const value = target[key]
// 如果值是Ref,则设置其对应的value属性值
if(value._v_isRef){
value.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
}
})
}
自动脱ref不仅存在于上述场景,reactive函数也有自动脱ref的能力,如下面代码所示:
const count = ref(0)
const obj = reactive({count})
obj.count //0
可以看到obj.count本应该是一个ref,但是由于有自动脱ref的能力在,所以不需要通过value属性就能读取ref的值,这样设计减少了用户的心智负担,可以不用关心哪些是ref,哪些不是ref