1.为什么要有ref
在JavaScript中,Proxy无法提供对原始值的代理,因此想要将原始值变成响应式数据,就必须对其做一层包裹,也就是ref。
对于一个原始值来说,我们没有办法拦截对它的任何操作,但是我们可以使用一个非原始值,去包裹一个原始值。
const wrapper = {
value: 'name'
}
const name = reactive(wrapper);
name.value; // name
// 修改值可以触发响应
name.value = 'volit';
为了保证规范性,我们将上面的代码段封装为ref函数。
function ref(val) {
const wrapper = {
value: val,
};
return reactive(wrapper);
}
现在,我们就可以使用ref函数创建原始值的响应变量了。
const name = ref("");
effect(() => {
console.log(name.value);
});
name.value = "volit"; // volit
为了让ref拥有自动脱ref的能力,我们还需要给ref对象设置一个专门的标记,用来识别对象是否是ref对象。
function ref(val) {
const wrapper = {
value: val,
};
Object.defineProperty(wrapper, "__v_isRef", {
value: true,
});
return reactive(wrapper);
}
2.响应丢失
通常我们可能会对一个reactive对象进行展开操作。
export default {
setup() {
const obj = reactive({foo: 1, bar: 2});
return {
...obj
}
}
}
然后在模板中进行访问:
<templete>
<p>
{{for}} / {{bar}}
</p>
</templete>
但是此时你会发现,obj对象的响应式特性丢失了,当我们修改obj的属性值的时候,并没有触发响应式操作。这是因为,展开运算符在在进行展开的时候,会把属性单独拆分出来,对于一个原始值属性,自然就丢失了响应式特性,对此,我们封装toRef和toRefs函数,对丢失响应特性的属性进行封装。
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key];
},
};
return wrapper;
}
function toRefs(obj) {
const ret = {};
for (const key in obj) {
ret[key] = toRef(obj, key);
}
return ret;
}
const obj = reactive({foo: 1, bar: 2});
const newObj = ...toRefs(obj);
同时为了和ref保持概念上的统一,我们同样对toRef创建出来的对象加入ref标记。
function toRef(obj, key) {
const wrapper = {
get value() {
return obj[key];
},
};
Object.defineProperty(wrapper, "__v_isRef", {
value: true,
});
return wrapper;
}
由于toRef函数只设置了get访问器,因此只能创建只读属性,对此我们给其加上set访问器。
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;
}
3.自动脱ref
虽然toRef函数解决了响应丢失的问题,但随即带了了增加用户心智负担的问题,因为对于每一个ref对象,我都要通过.value来获取它的值,同时我们在模板中,使用ref变量的值的时候,同样也不希望使用.value去访问变量的值,这时我们就可以使用到前面提到过的__v_isRef标记了,通过封装一个proxyRefs函数,判断属性是否拥有ref对象标记,来赋予属性自动脱ref的能力。同时,不仅在模板中具有自动脱ref的功能,reactive同样具有自动脱ref的能力。
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, newVal, receiver) {
const value = target[key];
if (value.__v_isRef) {
value.value = newVal;
return true;
}
return Reflect.set(target, key, newVal, receiver);
},
});
}