原始值指的是Boolean、Number、BigInt、String、Symbol、undefined和null等原始数据类型的值。
这个没有什么好讲的,主要就是基本数据类型的形参与实参之间无法建立联系,所以需要包裹一层进行数据代理。
但是有个点需要注意的是,如何判断一个代理对象为Ref创建的原始值代理,还是reactive创建的非原始值代理?
function ref(val) {
const wrapper = {
value: val
}
// 使用 Object.defineProperty 在 wrapper 对象上定义一个不可枚举的属性 __v_isRef,并且值为 true
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return reactive(wrapper)
}
从上面代码可知,如果是由ref
创建的响应式数据,会带有一个叫__v_isRef
的标记。
考虑如下代码
export default {
setup() {
// 响应式数据
const obj = reactive({ foo: 1, bar: 2 })
// 1s 后修改响应式数据的值,不会触发重新渲染
setTimeout(() => {
obj.foo = 0
},)
return {
...obj
}
}
}
上面代码中,将响应式数据通过展开符转换为另一个对象,导致响应丢失,此时我们可以通过toRef
与toRefs
来使其保持响应状态
function toRefs(obj) {
const ret = {}
// 使用 for...in 循环遍历对象
for (const key in obj) {
// 逐个调用 toRef 完成转换
ret[key] = toRef(obj, key)
}
return ret
}
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
}
使用例子
const obj = reactive({ foo: 1, bar: 2 })
const newObj = { ...toRefs(obj) }
console.log(newObj.foo.value) // 1
console.log(newObj.bar.value) // 2
自动脱Ref指的是,在模板函数中,我们可以直接使用ref的值来访问, 而不需要访问其value
属性。
如下代码,其中count
为一个ref值
<p>{{ count }}</p>
function proxyRefs(target) {
return new Proxy(target, {
get(target, key, receiver) {
const value = Reflect.get(target, key, receiver)
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)
}
})
}