原始值指boolean、number、bigInt、string、symbol、undefined、null等类型的值. 它们按值传递而不是按引用传递.
引入ref的概念
非原始值可以通过Proxy进行代理, 原始值可以通过创建一个对象进行代理. 封装ref函数实现此功能.
function ref(val){
const wrapper = {
value: val
}
// 设置__v_isRef 用以区分是否为ref
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return reactive(wrapper)
}
响应丢失问题
对响应式数据进行解构时, 会发生响应丢失的问题. 可以将响应式数据的每项转换为ref解决这个问题. 这就是toRef与toefs方法.
const obj = reactive({ foo: 1 })
const newObj = { ...obj }
// newObj.foo -> 1
// 解构后生成的 newObj 不再是一个响应式对象
const newObj = { ...toRefs(obj) }
// newObj.foo.value -> 1
// 解构后生成的 newObj 它的每项都是一个ref数据
function toRef(obj, key){
const wrapper = {
get value(){
return obj[key]
},
set value(val){
obj[key] = val
}
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return wrapper
}
function toRefs(obj){
const ret = {}
for(const key of obj){
ret[key] = toRef(obj, key)
}
return ret
}
自动脱ref
通过toRefs虽然解决了响应丢失问题, 但同时再次使用时需要.value增加心智负担. 我们需要在读取一个属性时, 如果其是ref, 则直接返回对应的.value, 实现自动脱ref.
const obj = reactive({ foo: 1 })
const newObj = proxyRefs({ ...toRefs(obj) })
// newObj.foo -> 1
function proxyRefs(target){
return new Proxy(target, {
// 拦截get操作, 当访问的值为ref 直接返回 .value
get(target, key, receiver){
const value = Reflect.get(target, key, receiver)
return val.__v_isRef ? val.value : val
},
// 修改时, 如果是ref则通过.value修改
set(target, key, newValue, receiver){
const val = target[key]
if(val.__v_isRef){
val.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
}
})
}
实际上setup函数返回的数据也会传递给proxyRefs进行处理. 因此在模版中不再需要.value.
总结
ref本质是一个原始值的包裹对象, 定义__v_isRef属性用以区分普通对象.toRef和toRefs解决了响应丢失问题.- 自动对暴露到模版的响应式数据进行脱ref处理.
reactive方法也有脱ref能力
const count = ref(0)
const obj = reactive({ count })
// obj.count -> 0