什么是原始值
原始值指的是 Boolean、Number、 BigInt、String、Symbol、undefined 和 null 等类型的值
引入 ref 的概念
Proxy第一个参数需要是对象,所以对原始值不起效果,需要用一个对象来包裹
解决思路:
- 封装一个 ref 函数用wrapper对象的value属性指向传入val
- 使用 Object.defineProperty 为包裹对象 wrapper 定义 了一个不可枚举且不可写的属性 __v_isRef,它的值为 true,代表 这个对象是一个 ref,而非普通对象。这样我们就可以通过检查 __v_isRef 属性来判断一个数据是否是 ref 了
- 将wrapper传入reactive转换成响应对象
js
// 封装一个 ref 函数
function ref(val) {
// 在 ref 函数内部创建包裹对象
const wrapper = {
value: val
}
// 使用 Object.defineProperty 在 wrapper 对象上定义一个不可枚举的属性 __v_isRef,并且值为 true
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
// 将包裹对象变成响应式数据
return reactive(wrapper)
}
// 创建原始值的响应式数据
const refVal = ref(1)
effect(() => {
// 在副作用函数内通过 value 属性读取原始值
console.log(refVal.value)
})
// 修改值能够触发副作用函数重新执行
refVal.value = 2
结果:
响应丢失问题
在effect读取的是新对象不是响应对象所以不会跟effect关联
js
// obj 是响应式数据
const obj = reactive({ foo: 1, bar: 2 })
// 将响应式数据展开到一个新的对象 newObj
const newObj = {
...obj
}
effect(() => {
// 在副作用函数内通过新的对象 newObj 读取 foo 属性值
console.log(newObj.foo)
})
// 很显然,此时修改 obj.foo 并不会触发响应
obj.foo = 100
解决思路:
- 当读取value的值时,返回obj对象下相应的属性值 ,使newObj有obj一样的属性,当在副作用函数 内读取 newObj.foo 时,等同于间接读取了 obj.foo 的值,设置setter使赋值newObj的属性能直接修改obj的属性
js
function toRef(obj, key) {
const wrapper = {
get value(){
return obj[key]
},
// 允许设置值
set value(val) {
obj[key] = val
}
}
// 定义 __v_isRef 属性
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return wrapper
}
function toRefs(obj) {
const ret = {}
for (const key in obj) {
ret[key] = toRef(obj, key)
}
return ret
}
// obj 是响应式数据
const obj = reactive({ foo: 1, bar: 2 })
// 将响应式数据展开到一个新的对象 newObj
const newObj = { ...toRefs(obj) }
effect(() => {
// 在副作用函数内通过新的对象 newObj 读取 foo 属性值
console.log(newObj.foo.value)
})
obj.foo = 100
结果:
自动脱 ref
如果读取的属性是一个 ref,则直接将该 ref 对应 的 value 属性值返回,减少访问难度
解决思路:
- newObj.foo是ref有__v_isRef属性,可以给ref外面再包一层,当有__v_isRef属性就通过value.value直接返回,否则直接返回value
- 当设置新值时判断是否有__v_isRef,如果有就说明是ref就赋值给value.value
js
function proxyRefs(target) {
return new Proxy(target, {
get(target, key, receiver) {
const value = Reflect.get(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)
}
})
}