ref
vue中是怎么对基本值继续代理的,Proxy无法对基本值(Boolean,String,Number等),使用vue3只用了vue2的Object.defineProperty对基本值进行代理
// 封装ref函数
function ref(val) {
const wrapper = {
value: val,
};
//定义一个不可枚举属性,并且值为true
Object.defineProperty(wrapper, '_v_isRef', {
value: true,
});
console.log(wrapper);
return reactive(wrapper);
}
const refVal = ref(1);
effect(() => {
console.log(refVal.value);
}); //1
refVal.value = 2; //2
使用Object.defineProperty为wrapper定义一个不可枚举属性,值为true,代表对象是一个ref,这样就可以通过__V_isRef判断这个是不是ref. 为什么需要这个不可枚举属性
const refVal1 = ref(1)
const refVal2=reactive({value:1})
如果没有上面那个不可枚举属性,就没办法区分是原始值还是非原始值对象
响应丢失
ref可以解决响应丢失问题,什么是响应丢失?
const obj = reactive({foo:1,bar:2})
//将对象扩展
const newObj = {...obj}
effect(()=>{
console.log(newObj.foo)
)
obj.foo =100//不会触发响应
如上面代码,修改obj.foo的值并不会触发响应,这是因为通过扩展运算符展开的是一个普通对象,没有响应能力. vue中解决方法如下 封装toRef函数
//封装toRef函数
function toRef(obj,key){
const wrapper={
get value(){
return obj[key]
}
}
return wrapper
}
const newObj = {
foo:toRef(obj,'foo'),
bar:toRef(obj,'bar')
}
原理其实就是重新在响应式数据中访问,把该值包装成对象,key为value返回,当然,还要考虑要转换很多属性的情况
//封装toRefs函数
function toRefs(obj){
const ret = {}
for(const key in obj){
ret[key] = toRef(obj,key)
}
return ret
}
原理是一样的,只是for...in循环批量封装,现在我们要将转换的数据真正转为ref:为每一个数据加上不可枚举属性
//封装toRef函数
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key];
},
};
Object.defineProperty(wrapper, '__v_isRef', {
value: true,
});
return wrapper;
}
现在,ref数据可读了,还需要可以设置
//封装toRef函数
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;
}
通过执行set value方法进行赋值,触发响应
脱离ref
我们封装的toRefs方法会把对象第一层封装为ref,用户使用就必须newObj.foo.value,这其实并不方便. vue中设计了自动脱离ref的功能,就是当访问属性是一个ref时,则直接将对应的value返回.要实现这个功能,vue是给newObj用proxy创建代理对象
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
}
})
}
//调用函数创建代理
const newObj = proxyRefs=({...toRefs(obj)})
console.log(newObj.foo)
console.log(newObj.bar)
读取属性有脱离ref功能,设置属性也应该自动为ref设置的能力
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, newVal, receiver) {
const value = target[key];
//如果值是Ref,则设置对应value
if (value.__v_isRef) {
value.value = newVal;
return true;
}
return Reflect.set(target, key, newVal, receiver);
},
});
}
判断是不是ref,是则为它的value更改值