1.写在前面。javascript中的原始值包括:Boolean、String、Number、Null、Undefined、Symbol、BigInt等类型。原始值按按值传递而不是按引用传递排序。之前我们知道Proxy可以实现object类型的响应式代理,但是无法实现原始值的代理。要实现将原始值转换为响应式数据,需要进行一些处理。2、refProxy的代理目标必须是对象类型,那么可不可以把原来的值类型包装成对象类型,从而实现代理呢?//letname="pingping"constdata={value:"pingping"}conststate=reactive(data);name.value="一川";想法很好,但是你有没有想过不这样做带来的问题:用户在创建一个带有原始值的响应式数据时,必须创建一个包装对象。而包对象是用户自己定义的,所以在命名和使用上都存在不规范之处。解决方法很简单。不担心自定义对象不规范、不可控吗?然后只需在源代码中定义它们。functionref(val){constwrapper={value:val}returnreactive(wrapper);}简单的尝试:constrefVal=ref("pingping");effect(()=>{console.log(refVal.value);});refVal.value="onechuan";但是在使用过程中还有一个问题:如何保证refVal是一个原始值的包装对象,还是一个非原始值的响应式数据?constrefVal=ref("pingping");constrefVal2=reactive({value:"pingping"});其实ref和reactive生成??响应式数据的实现是一样的,区分数据源是否为ref的目的是为了后续移除ref,出于响应式的能力恢复原始数据。functionref(val){constwrapper={value:val}Object.defineProperty(wrapper,"__v_isRef",{value:true})returnreactive(wrapper);}在上面的代码中,使用Object.defineProperty来包装对象wrapper定义一个不可枚举不可写的属性“__v_isRef”,其值为true以区分当前对象是ref而不是普通对象。简而言之:ref其实是对一个对象的二次封装,是响应式的。3.响应丢失的问题我们知道ref可以用来实现原值的响应代理,但是它也可以用来解决响应丢失的问题。所谓失去响应性,是指使用reactive生成的响应式对象数据。使用扩展运算符(...)将失去响应性并成为普通对象数据。此时修改被修改对象的属性值不会触发更新和模板渲染。constobj=reactive({name:"pingping"});constnewObj={...obj};effect(()=>{console.log(newObj.name);});obj.nmae="onechuan";上述代码中,在副作用函数中只访问了普通对象newObj的属性名的值,不具备响应能力。当其属性值被修改时,不会触发副作用函数重新执行。那么,如何解决响应性损失的问题呢?其实可以在sideeffectfunction中解决,通过获取普通对象newObj的属性值,也会触发update和sideeffectfunction建立连接。通过在普通对象newObj中设置与obj对象同名的属性,将每个属性值设置为一个对象,通过对象的getvalue方法读取obj对象的属性值,使得属性newObj的值和Side-effect函数建立联系。constobj=reactive({name:"pingping"});constnewObj={...obj};effect(()=>{console.log(newObj.name);});obj.nmae="onechuan";但是,如果obj对象中有很多属性,是否需要在newObj中创建很多同名的对象呢?然后,您可以提取并包装函数:functiontoRef(obj,key){constwrapper={getvalue(){returnobj[key];},setvalue(val){obj[key]=val}}Object.defineProperty(wrapper,"__v_isRef",{value:true})returnwrapper;}在使用过程中,简单:constobj=reactive({name:"pingping"});constname=toRef(obj,"name");name.value="onechuan";前面只是像这样将几个对象的属性值转换成响应式数据,但是当我们需要批量处理数据时,应该怎么处理呢?很简单,遍历对象属性还不够。函数toRefs(obj){constres={};for(obj中的常量键){res[key]=toRef(obj,key);}returnres;}这样就解决了responsiveloss的问题,方法是将responsive数据转换成类似ref结构的数据,通过toRef或toRefs转换后得到的数据才是真正的ref数据。4.自动去掉ref,使用toRefs解决响应丢失的问题,就是遍历对象的属性,转换为ref,会带来新的问题,即访问数据的一级属性,它必须通过.value访问。这无疑会增加用户的心理负担,而用户肯定愿意直接使用该对象。属性而不是通过对象使用属性值。属性。价值。constobj=reactive({name:"pingping",age:18});constnewObj={...toRefs(obj)};newObj.name.value//pingpingnewObj.age.value//18现在我们需要让它会自动移除ref,这样在访问对象属性时,如果属性被读取为ref,则返回ref.value,否则直接返回属性值。functionproxyRefs(target){returnnewProxy(target,{get(target,key,receiver){constvalue=Reflect.get(target,key,receiver);returnvalue.__v_isRef?value.value:value;}})}constnewObj=proxyRefs(...toRefs(obj));上面代码中定义了一个proxyRefs函数,接收一个对象参数,返回该对象的代理对象。代理对象的作用是当读取对象的属性为ref值时,使用get操作直接返回ref.value值,否则直接返回属性值,从而实现ref的自动移除。实际上,在模板中使用ref的属性值时,会将组件设置返回的数据传递给proxyRefs函数进行处理。这使得直接访问模板中的属性值而不是attribute.value值成为可能。之前有自动脱ref的功能,现在有自动穿ref的功能。实现原理也是通过添加相应的集合拦截函数。functionproxyRefs(target){returnnewProxy(target,{get(target,key,receiver){constvalue=Reflect.get(target,key,receiver);returnvalue.__v_isRef?value.value:value;},设置(target,key,newValue,receiver){constvalue=target[key];if(value.__v_isRef){value.value=newValue;returntrue}returnReflect.set(target,key,newValue,receiver);}}复制代码)}5.写在最后。本文主要介绍如何将原始值转化为响应式数据,如何解决响应式丢失的问题,以及如何减轻用户的心理负担实现自动移除ref的能力等。ref的本质是一个封装object,通过reactive实现对原始值的响应式代理,但是封装对象和普通对象本质上是一样的。为此,需要设置一个标识符__v_isRef来实现ref数据的区分。
