全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-07-31_源码视角,Vue3为什么推荐使用ref而不是reactive

您的位置:首页 >> 新闻 >> 行业资讯

源码视角,Vue3为什么推荐使用ref而不是reactive 点击公众关注号,“技术干货”及时达!ref 和 reactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型,而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象,但 Vue3 官方文档更推荐使用 ref。 我的想法,ref就是比reactive好用,官方也是这么说的,不服来踩!下面我们从源码的角度详细讨论这两个 API,以及 Vue3 为什么推荐使用ref而不是reactive? ref 的内部工作原理ref是一个函数,它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个.value属性,该属性指向内部值。 //深响应式 exportfunctionref(value?:unknown){ returncreateRef(value,false) } //浅响应式 exportfunctionshallowRef(value?:unknown){ returncreateRef(value,true) } functioncreateRef(rawValue:unknown,shallow:boolean){ //如果传入的值已经是一个ref,则直接返回它 if(isRef(rawValue)){ returnrawValue } //否则,创建一个新的RefImpl实例 returnnewRefImpl(rawValue,shallow) } classRefImplT{ //存储响应式的值。我们追踪和更新的就是_value。(这个是重点) private_value:T //用于存储原始值,即未经任何响应式处理的值。(用于对比的,这块的内容可以不看) private_rawValue:T //用于依赖跟踪的Dep类实例 publicdep?:Dep=undefined //一个标记,表示这是一个ref实例 publicreadonly__v_isRef=true constructor( value:T, publicreadonly__v_isShallow:boolean, ){ //如果是浅响应式,直接使用原始值,否则转换为非响应式原始值 this._rawValue=__v_isShallow?value:toRaw(value) //如果是浅响应式,直接使用原始值,否则转换为响应式值 this._value=__v_isShallow?value:toReactive(value) //toRaw用于将响应式引用转换回原始值 // toReactive 函数用于将传入的值转换为响应式对象。对于基本数据类型,toReactive 直接返回原始值。 //对于对象和数组,toReactive 内部会调用 reactive 来创建一个响应式代理。 //因此,对于ref来说,基本数据类型的值会被RefImpl直接包装,而对象和数组 //会被 reactive 转换为响应式代理,最后也会被 RefImpl 包装。 //这样,无论是哪种类型的数据,ref都可以提供响应式的value属性, //使得数据变化可以被 Vue 正确追踪和更新。 //exportconsttoReactive=(value)=isObject(value)?reactive(value):value } getvalue(){ //追踪依赖,这样当 ref 的值发生变化时,依赖这个 ref 的组件或副作用函数可以重新运行。 trackRefValue(this) //返回存储的响应式值 returnthis._value } setvalue(newVal){ //判断是否应该使用新值的直接形式(浅响应式或只读) constuseDirectValue= this.__v_isShallow||isShallow(newVal)||isReadonly(newVal) //如果需要,将新值转换为非响应式原始值 newVal=useDirectValue?newVal:toRaw(newVal) //如果新值与旧值不同,更新_rawValue和_value if(hasChanged(newVal,this._rawValue)){ this._rawValue=newVal this._value=useDirectValue?newVal:toReactive(newVal) //触发依赖更新 triggerRefValue(this,DirtyLevels.Dirty,newVal) } } } 在上述代码中,ref 函数通过 new RefImpl(value) 创建了一个新的 RefImpl 实例。这个实例包含 getter 和 setter,分别用于追踪依赖和触发更新。使用 ref 可以声明任何数据类型的响应式状态,包括对象和数组。 import{ref}from'vue' letstate=ref({count:0}) state.value.count++ 注意,ref核心是返回「响应式且可变的引用对象」,而reactive核心是返回的是「响应式代理」,这是两者本质上的核心区别,也就导致了ref优于reactive,我们接着看下reactive源码实现。 reactive 的内部工作原理reactive 是一个函数,它接受一个对象并返回该对象的响应式代理,也就是 Proxy。 functionreactive(target){ if(targettarget.__v_isReactive){ returntarget } returncreateReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) } functioncreateReactiveObject( target, isReadonly, baseHandlers, collectionHandlers, proxyMap ){ if(!isObject(target)){ returntarget } constexistingProxy=proxyMap.get(target) if(existingProxy){ returnexistingProxy } constproxy=newProxy(target,baseHandlers) proxyMap.set(target,proxy) returnproxy } reactive的源码相对就简单多了,reactive 通过 new Proxy(target, baseHandlers) 创建了一个代理。这个代理会拦截对目标对象的操作,从而实现响应式。 import{reactive}from'vue' letstate=reactive({count:0}) state.count++ 到这里我们可以看出 ref 和 reactive 在声明数据的响应式状态上,底层原理是不一样的。ref 采用 RefImpl对象实例,reactive采用Proxy代理对象。 ref 更深入的理解当你使用new RefImpl(value)创建一个RefImpl实例时,这个实例大致上会包含以下几部分: 「内部值」:实例存储了传递给构造函数的初始值。「依赖收集」:实例需要跟踪所有依赖于它的效果(effect),例如计算属性或者副作用函数。这通常通过一个依赖列表或者集合来实现。「触发更新」:当实例的值发生变化时,它需要通知所有依赖于它的效果,以便它们可以重新计算或执行。RefImpl 类似于发布-订阅模式的设计,以下是一个简化的RefImpl类的伪代码实现,展示这个实现过程: classDep{ constructor(){ this.subscribers=newSet(); } depend(){ if(activeEffect){ this.subscribers.add(activeEffect); } } notify(){ this.subscribers.forEach(effect=effect()); } } letactiveEffect=null; functionwatchEffect(effect){ activeEffect=effect; effect(); activeEffect=null; } classRefImpl{ constructor(value){ this._value=value; this.dep=newDep(); } getvalue(){ //当获取值时,进行依赖收集 this.dep.depend(); returnthis._value; } setvalue(newValue){ if(newValue!==this._value){ this._value=newValue; //值改变时,触发更新 this.dep.notify(); } } } //使用示例 letcount=newRefImpl(0); watchEffect(()={ console.log(`Thecountis:${count.value}`);//订阅变化 }); count.value++;//修改值,触发通知,重新执行watchEffect中的函数 Dep 类负责管理一个依赖列表,并提供依赖收集和通知更新的功能。RefImpl 类包含一个内部值 _value 和一个 Dep 实例。当 value 被访问时,通过 get 方法进行依赖收集;当 value 被赋予新值时,通过 set 方法触发更新。 ref 和 reactive 尽管两者在内部实现上有所不同,但它们都能满足我们对于声明响应式变量的要求,但是 reactive 却存在一定的局限性。 reactive 的局限性在 Vue3 中,reactive API 通过 Proxy 实现了一种响应式数据的方法,尽管这种方法在性能上比 Vue2 有所提升,但 Proxy 的局限性也导致了 reactive 的局限性,这些局限性可能会影响开发者的使用体验。 仅对引用数据类型有效reactive 主要适用于对象,包括数组和一些集合类型(如 Map 和 Set)。对于基础数据类型(如 string、number 和 boolean),reactive 是无效的。这意味着如果你尝试使用 reactive 来处理这些基础数据类型,将会得到一个非响应式的对象。 import{reactive}from'vue'; conststate=reactive({count:0 使用不当会失去响应「直接赋值对象」:如果直接将一个响应式对象赋值给另一个变量,将会失去响应性。这是因为 reactive 返回的是对象本身,而不仅仅是代理。 import{reactive}from'vue'; letstate=reactive({count:0 state={count:1//失去响应性 「直接替换响应式对象」:同样,直接替换一个响应式对象也会导致失去响应性。 import{reactive}from'vue'; letstate=reactive({count:0 state=reactive({count:1//失去响应性 「直接解构对象」:在解构响应式对象时,如果直接解构对象属性,将会得到一个非响应式的变量。 conststate=reactive({count:0 let{count}=state; count++;//count仍然是0 解决这个问题,需要使用 toRefs 函数来将响应式对象转换为 ref 对象。 import{toRefs}from'vue'; conststate=reactive({count:0 let{count}=toRefs(state); count++;//count现在是1 「将响应式对象的属性赋值给变量」:如果将响应式对象的属性赋值给一个变量,这个变量的值将不会是响应式的。 letstate=reactive({count:0}) letcount=state.count count++//count仍然是0 console.log(state.count) 使用 reactive 声明响应式变量的确存在一些不便之处,尤其是对于喜欢使用解构赋值的开发者而言。这些局限性可能会导致意外的行为,因此在使用 reactive 时需要格外注意。相比之下,ref API 提供了一种更灵活和统一的方式来处理响应式数据。 为什么推荐使用 ref ?ref()它为响应式编程提供了一种统一的解决方案,适用于所有类型的数据,包括基本数据类型和复杂对象。以下是推荐使用 ref 的几个关键原因: 统一性ref 的核心优势之一是它的统一性。它提供了一种简单、一致的方式来处理所有类型的数据,无论是数字、字符串、对象还是数组。这种统一性极大地简化了开发者的代码,减少了在不同数据类型之间切换时的复杂性。 import{ref}from'vue'; letnum=ref(0); letstr=ref('Hello'); letobj=ref({count:0 //修改基本数据类型 num.value++; str.value+='World'; //修改对象 obj.value.count++; 深层响应性ref 支持深层响应性,这意味着它可以追踪和更新嵌套对象和数组中的变化。这种特性使得 ref 非常适合处理复杂的数据结构,如对象和数组。 import{ref}from'vue'; letobj=ref({ user:{ name:'xiaoming', details:{ age:18 } } }); //修改嵌套对象 obj.value.user.details.age++; 当然,为了减少大型不可变数据的响应式开销,也可以通过使用shallowRef来放弃深层响应性。 letshallowObj=shallowRef({ details:{age:18,}, }); 灵活性ref 提供了高度的灵活性,尤其在处理「普通赋值」方面。这种灵活性使得 ref 在开发中的使用更加方便,特别是在进行复杂的数据操作时。 import{ref}from'vue'; letstate=ref({ count:0, name:'Vue' }); //替换整个对象 state.value={ count:10, name:'Vue4' }; //修改对象内的属性 state.value.count=20; state.value.name='Vue5'; //添加新的属性 state.value.newProperty='NewProperty'; //删除属性 deletestate.value.newProperty; //使用解构更新属性(注意要保持响应性) let{count,name}=state.value; state.value={count:count+1,name //复杂操作,例如根据条件更新属性 if(someCondition){ state.value={ ...state.value, name:'UpdatedName' } console.log(state.value) 总结ref 在 Vue3 中提供了一种更统一、灵活的响应式解决方案,还能避免了 reactive 的某些局限性。希望这篇文章对你有所帮助,有所借鉴。大家怎么认为呢,评论区我们一起讨论下! 点击公众关注号,“技术干货”及时达! 阅读原文

上一篇:2016-09-29_一周:拒绝年龄歧视!加州立法要求IMDB删除年龄数据 下一篇:2025-06-02_疯狂打Call,哈工大斩获AI顶会ACL评审阶段最高分

TAG标签:

19
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价