当前位置: 首页 > Web前端 > vue.js

Vue3实战——全面掌握ref、reactive_0

时间:2023-03-31 17:13:49 vue.js

我知道你在使用Vue3时是否有这样的疑惑,“ref、rective可以创建响应式对象,我应该如何选择?”、“为什么响应式对象被解构了?失去了响应性?”我该如何应对?”今天我们就来全面盘点一下ref和reactive,相信看完你会有不一样的收获,一起来学习吧!reactive()的基本用法在Vue3中,我们可以使用reactive()来创建一个响应式对象或者数组:import{reactive}from'vue'conststate=reactive({count:0})这个响应式对象其实就是一个Proxy,Vue会在访问这个Proxy的属性时收集副作用,在修改属性时触发副作用。要在组件模板中使用反应状态,需要在setup()函数中定义并返回它。当然你也可以使用响应式代理vs原始对象reactive()返回原始对象的Proxy,它们不相等:constraw={}constproxy=reactive(raw)console.log(proxy===raw)//false原始对象在它也可以在模板中使用,但修改原始对象不会触发更新。因此,要使用Vue的响应式系统,就必须使用代理。为了保证访问代理的一致性,在同一个原始对象上调用reactive(),总是会返回同一个代理对象,而一个已经存在的代理调用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()虽然强大,但也有以下局限:只对对象类型(对象、数组、Map、Set等集合类型)有效,对string、number等原始类型无效,和布尔值。因为Vue的反应系统是通过属性访问来跟踪的,如果我们直接“替换”一个反应对象,这将导致与初始引用的反应连接丢失:将响应对象的属性赋值或解构为局部变量,或者设置When一个属性传入一个函数,它会失去响应性:conststate=reactive({count:0})//n是一个局部变量,state.count失去响应连接letn=state.count//不会影响state++//count也失去了与state.count的响应连接let{count}=state//不影响statecount++//参数count也失去了与state.count的响应连接functioncallSomeFunction(count){//不影响状态count++}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()来处理这个值。参考Vue实战视频讲解:回车学习包含对象的ref,可以响应式替换整个对象:ref从通用对象解构属性或将属性传递给函数时不损失响应能力:conststate={count:ref(0)}//解构后,仍然和state.count保持响应式连接const{count}=state//会影响statecount.value++//这个函数接收一个ref,和传入的保持响应式连接valuefunctioncallSomeFunction(count){//影响状态count.value++}callSomeFunction(state.count)ref()允许我们使用任何值类型创建ref对象并传递t在不失去反应性的情况下下摆。此功能非常重要,经常用于将逻辑提取到复合??函数中。//mouse.jsexportfunctionuseMouse(){constx=ref(0)consty=ref(0)//...return{x,y}}ref的拆包所谓拆包就是获取ref对象上的value属性的值。两个常用的方法是.value和unref()。unref()是Vue提供的一个方法。如果参数是ref,则返回value属性的值,否则返回参数本身。在模板中展开refs当refs在模板中作为顶级属性访问时,它们会自动展开,无需使用.value。这是前面的例子,使用ref()代替:还有一种情况,如果文本插值({{}})计算的最终值是ref,也会自动展开。以下非顶级属性将被正确呈现。其他情况不会自动解包,如: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}}
现在结果将是正确的呈现出来。在反应式对象中展开引用当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)//1ref数组和集合类型中对象的解包和响应不同,当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的响应式系统提升了一个层次!