上一章学习了非原始值的响应式方案,本章学习的是原始值响应式方案,原始值指的是Boolean、Number、BigInt、String、Symbol、undefined和null等类型的值,在JS中原始值是值传递。在这种情况下,如果函数接收一个原始值参数则,形参与实参相互独立,无法相互引用,修改形参不会影响实参,而且Proxy无法代理原始值数据,原始值变为响应式数据,则需要使用ref。
1、ref包装
Proxy无法代理原始值数据,只能使用一个非原始值包裹原始值。但进行包裹会出现两个问题:
1、用户创建一个响应式的原始值,不得不顺带创建一个对象进行包裹
2、用户定义包裹对象,这意味着不规范,可以随意命名
解决方案:创建一个函数把包裹对象创建封装在函数中,同时还需要判断一个数据是否是ref,使用Object.defineProperty在wrapper对象上定义一个不可枚举的属性,并且值为true,代码如下:
function ref(val) {
const wrapper = {
value: val
}
Object.defineProperty(wrapper, '__v_isRef', {
value: true
})
return reactive(wrapper)
}
2、响应丢失问题
什么是响应丢失?
响应丢失:指的是在需要返回的数据为响应式时,返回的却是普通对象,不具有响应式能力,暴露出后,模板使用不会重新触发渲染。
ref也可以解决响应丢失的问题
封装toRef方法,设置一个访问器属性value,当读取value的值时最终访问的是,响应式下同名属性值。toRef 函数接收两个参数,第一个参数 obj 是一个响应式数据,第二个参数是 obj 对象的一个键。代码如下:
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
}
如果obj的key非常多,则需要通过添加for in来解决
function toRefs(obj) {
const ret = {}
for (const key in obj) {
ret[key] = toRef(obj, key)
}
return ret
}
3、自动脱ref
toRefs解决了响应丢失的问题,但会增加用户负担,toRefs会把响应式数据的第一层转换为ref,用户必须通过value属性访问值。
解决方案:需要有自动脱ref的能力,即读取的属性是一个ref则把对应的value属性值直接返回。使用Proxy创建一个代理对象,通过代理实现最终目标,代码如下:
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) {
const value = target[key]
if (value.__v_isRef) {
value.value = newValue
return true
}
return Reflect.set(target, key, newValue, receiver)
}
})
}
代理对象拦截get、set操作,读取或者设置到一个ref时,直接返回或者设置ref的value属性值。
总结
本章学习了:
1、ref的概念,本质上是通过包裹对象实现响应式,因为JS的Proxy无法对原始值代理
2、ref可以解决响应丢失的问题,通过对响应式数据做了一层包装,实现了toRef和toRefs
3、自动脱ref,对暴露到模板中的响应式数据进行脱 ref 处理,减轻用户负担