不知道你在使用Vue3时是否有这样的疑惑,“ref和rective都可以??创建响应式对象,我该如何选择?”,“为什么响应式对象在解构后会失去响应性?如何处理?”今天就来全面盘点一下ref和reactive。相信看完所有的响应式问题,都会迎刃而解。让我们一起学习吧!reactive()的基本用法在Vue3中,我们可以使用reactive()来创建一个响应式对象或者数组:import{reactive}from'vue'conststate=reactive({count:0})这个响应式对象其实就是一个Proxy,Vue会在访问这个Proxy的属性时收集副作用,在修改属性时触发副作用。要在组件模板中使用反应状态,需要在setup()函数中定义并返回它。{{state.count}}
当然你也可以使用{{state.count}}
响应式代理vs原始对象reactive()返回原始对象的Proxy,它们不相等:constraw={}constproxy=reactive(raw)console.log(proxy===raw)//false原始对象在它也可以在模板中使用,但修改原始对象不会触发更新。因此,要使用Vue的响应式系统,就必须使用代理。为了保证访问代理的一致性,在同一个原始对象上调用reactive()总是会返回同一个代理对象,而对于一个已经存在的Proxy调用reactive()的对象将返回自身:constraw={}constproxy1=reactive(raw)constproxy2=reactive(raw)console.log(proxy1===proxy2)//trueconsole.log(reactive(proxy1)===proxy1)//true此规则也适用于嵌套对象。依赖深度响应,响应对象中的嵌套对象仍然是一个代理:constraw={}constproxy=reactive({nested:raw})constnested=reactive(raw)console.log(proxy.nested===nested)//trueshallowReactive()在Vue中,状态默认是深度响应的。但是在某些场景下,我们可能希望创建一个浅层的反应对象,让它只在顶层响应,这时我们可以使用shallowReactive()。conststate=shallowReactive({foo:1,nested:{bar:2}})//状态本身的属性是反应性的state.foo++//底层嵌套对象不是反应性的,不会按预期状态工作。nested.bar++注意:浅反应对象应该只用于组件中的根级状态。避免将其嵌套在反应对象内部,因为其内部的属性具有不一致的反应行为,嵌套后将难以理解和调试。reactive()的局限性reactive()虽然强大,但也有以下局限性:1、只对对象类型(对象、数组、Map、Set等集合类型)有效,而对string等基元有效,number,和boolean类型无效。2.因为Vue的反应系统是通过属性访问来跟踪的,如果我们直接“替换”一个反应对象,这将导致与初始引用的反应连接丢失:3.将响应对象的属性赋值或解构给局部变量,或者属性传入函数时,会失去响应性:conststate=reactive({count:0})//n为局部变量,state.count失去响应性连接letn=state.count//does不影响状态n++//count也失去了与state.count的响应连接let{count}=state//不影响状态count++//参数count也失去了与state.count的响应连接hstate.countfunctioncallSomeFunction(count){//不会影响statecount++}callSomeFunction(state.count)为了解决上面的限制,ref来了!ref()Vue提供了一个ref()方法,允许我们使用任何值类型创建响应式引用。基本用法ref()将传入的参数包装到具有value属性的ref对象中:import{ref}from'vue'constcount=ref(0)console.log(count)//{value:0}count.value++console.log(count.value)//1和响应式对象的属性类似,ref的value属性也是响应式的。同时,当值是对象类型时,Vue会自动使用reactive()来处理这个值。包含对象的ref可以反应性地替换整个对象:ref从一般对象解构属性或将属性传递给函数时,不会有响应性的损失:conststate={count:ref(0)}//解构后,andstate.count仍然保持响应连接const{count}=state//会影响statecount.value++//这个函数接收一个ref,传入的值仍然保持响应连接functioncallSomeFunction(count){//会影响状态计数。value++}callSomeFunction(state.count)ref()允许我们使用任何值类型创建ref对象并传递它们而不会失去响应能力。此功能非常重要,经常用于将逻辑提取到复合??函数中。//mouse.jsexportfunctionuseMouse(){constx=ref(0)consty=ref(0)//...return{x,y}}ref的拆包所谓拆包就是获取ref对象上的value属性的值。两个常用的方法是.value和unref()。unref()是Vue提供的一个方法。如果参数是ref,则返回value属性的值,否则返回参数本身。1.模板中ref的解包当ref作为模板中的顶级属性访问时,会自动解包,不需要使用.value。这是前面的例子,使用ref()代替:{{count}}
还有一种情况,如果文本插值({{}})计算的最终值是ref,也会自动展开。以下非顶级属性将被正确呈现。{{object.foo}}
其他情况不会自动解包,如:object.foo不是顶级属性,文本插值({{}})计算的最终值不是ref:constobject={foo:ref(1)}以下将不会按预期工作:
{{object.foo+1}}
呈现的结果将是[objectObject]1,因为object.foo是一个ref对象。我们可以通过将foo更改为顶级属性来解决此问题:constobject={foo:ref(1)}const{foo}=object
{{foo+1}}
现在结果将是正确渲染出来。2.响应式对象中ref的解包当ref嵌套在响应式对象中,作为属性访问或更改时,它会自动解包,因此它的行为与一般属性相同:constcount=ref(0)conststate=reactive({count})console.log(state.count)//0state.count=1console.log(state.count)//1仅当嵌套在深度反应对象中时,才会发生解包。当ref作为浅反应对象的属性访问时,它不会被解包:constcount=ref(0)conststate=shallowReactive({count})console.log(state.count)//{value:0}而不是0,如果将新的ref分配给与ref关联的属性,那么它将替换旧的ref:constcount=ref(1)conststate=reactive({count})constotherCount=ref(2)state.count=otherCountconsole.log(state.count)//2//count此时已经和state.count失去联系console.log(count.value)//13.ref解包响应数组和集合类型与类型对象不同,当ref作为响应式数组或本地集合类型(如Map)的元素访问时,它不会被解包。constbooks=reactive([ref('Vue3Guide')])//需要.valueconsole.log(books[0].value)constmap=reactive(newMap([['count',ref(0)]]))//这里需要。valueconsole.log(map.get('count').value)toRef()toRef是一种根据响应对象上的属性创建相应ref的方法。这样创建的ref与其source属性保持同步:更改source属性的值将更新ref的值,反之亦然。conststate=reactive({foo:1,bar:2})constfooRef=toRef(state,'foo')//改变源属性更新refstate.foo++console.log(fooRef.value)//2//更改ref也会更新源属性fooRef.value++console.log(state.foo)//3toRef()当你想将prop的ref传递给复合函数时很有用:当toRef与组件props结合使用时,对props进行更改的限制仍然有效。如果将新值传递给ref等同于尝试直接改变props,这是不允许的。在这种情况下,您可以考虑使用computedwithget和set代替。注意:即使源属性当前不存在,toRef()也会返回一个可用的引用。这使得它在处理可选道具时很有用,而toRefs不会为可选道具创建相应的引用。让我们看看下面的toRefs。toRefs()toRefs()是一种将响应式对象上的所有属性转换为refs,然后将这些refs组合成一个公共对象的方法。此公共对象的每个属性都与源对象的属性保持同步。conststate=reactive({foo:1,bar:2})//相当于//conststateAsRefs={//foo:toRef(state,'foo'),//bar:toRef(state,'bar')//}conststateAsRefs=toRefs(state)state.foo++console.log(stateAsRefs.foo.value)//2stateAsRefs.foo.value++console.log(state.foo)//3来自组合函数toRefs在返回反应对象时很有用。它允许我们在不损失响应性的情况下解构返回的对象://feature.jsexportfunctionuseFeature(){conststate=reactive({foo:1,bar:2})//...//将返回的所有属性都转换torefreturntoRefs(state)}toRefs只会创建源对象上已存在的属性的引用。如果你想为一个还不存在的属性创建一个ref,你需要使用上面提到的toRef。以上就是ref和reactive的详细用法。不知道大家有没有新的收获。接下来,让我们讨论响应性原则。响应式原理Vue2的限制我们都知道Vue2中的响应式使用Object.defineProperty()通过getter/setter来拦截属性。这种方式对老版本浏览器的支持比较友好,但是也有很多缺点:初始化时只会对已有的对象属性进行响应式处理。也就是说,在添加或删除属性时,Vue无法对其进行监控。必须使用特殊的API来处理。该数组是通过覆盖原型对象上的7个方法来实现的。如果通过下标修改了数据,Vue也是无意识的。还使用特殊的API处理。无法处理Map和Set等集合类型。具有反应状态的逻辑不容易重用。Vue3的响应式系统针对以上情况,Vue3的响应式系统诞生了!Vue3使用Proxy创建响应式对象,ref只使用getter/setter,完美解决了上述限制。下面的代码可以说明它们是如何工作的:{target[key]=valuetrigger(target,key)}})}functionref(value){constrefObject={getvalue(){track(refObject,'value')返回值},setvalue(newValue){value=newValuetrigger(refObject,'value')}}returnrefObject}不难看出,当将响应对象的属性解构为局部变量时,响应性会“断开连接”。因为访问局部变量不会触发get/set代理捕获。让我们回到响应原则。在track()内部,我们检查当前是否有任何副作用正在运行。如果有,它会找到存储所有跟踪该属性的订阅者的Set,然后将当前副作用作为新的订阅者添加到该Set中。//activeEffect将在副作用即将运行之前设置存储在一个全局的WeakMap
>>数据结构中。如果在第一次跟踪时没有找到相应属性订阅的副作用集合,则会在此处创建。这就是getSubscribersForProperty()函数的作用。在trigger()中,我们将再次找到该属性的所有订阅副作用。这次我们全部执行:functiontrigger(target,key){consteffects=getSubscribersForProperty(target,key)effects.forEach((effect)=>effect())}这些副作用用于执行diff算法更新的页面。这是响应式系统的一般原理。Vue3还优化了编译器,优化了diff算法等等。不得不佩服游达,他把Vue的响应式系统提升了一个层次!好了,今天的分享就到这里。不知道大家对ref和reactive有没有更深入的理解。欢迎在评论区留言。有一点感触记得点赞支持哦!