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

Vue响应式原理解析——数据更新常见问题

时间:2023-04-01 00:21:40 vue.js

概述在Vue开发过程中,会出现页面不更新,数据更新后渲染等问题。在上篇《Vue 响应式原理剖析 —— 从实例化、渲染到数据更新(上)》和《Vue 响应式原理剖析 —— 从实例化、渲染到数据更新(下)》两篇文章中,从“实例化”、“渲染”、“数据更新”三行完整的讲述了Vue“响应式”的工作原理。这篇文章就是基于这些原则来解决一些常见的数据更新相关的问题。无法检查对象数据的某些修改?在下面的场景中,obj.message是否可以在赋值时监听并响应?varvm=newVue({data:{obj:{a:1},},template:'

{{obj.message}}
'});虚拟机。对象。消息='修改';答案无法监控。原因:Object.defineProperty无法监控对象属性的增删改查。上面说了Vue的数据响应是基于Object.defineProperty的,所以也有局限性。解决方案:Vue提供了一个具体的方法vm.$set(obj,propertyName,newValue)来处理这种情况。至于这个方法的具体逻辑,后面会详细说明。数组数据的某些修改无法监控?在下面的场景中,三种赋值语句中的哪一种可以被监听和响应?constvm=newVue({数据:{项目:[1,2,3,4,5],},});虚拟机。项目[1]=8;虚拟机。项目[5]=6;虚拟机。项目.length=2;答案是这三种操作都无法监控。原因:第二个操作vm.items[5]=6应该比较明显。与上面对象属性的增删类似,数组的新增和删除元素无法被Object.defineProperty监听到。第三个操作vm.items.length=2也是由于Object.defineProperty的限制,即使直接修改也无法监听到数组的长度。最容易误判的可能是第一个操作vm.items[1]=8。更常规的说法是Object.defineProperty不支持监听数组元素的变化。为了验证这个说法,你可以直接用一个例子来说明真实的Condition。示例如下:functiondefineReactive(obj,key,val){Object.defineProperty(obj,key,{enumerable:true,configurable:true,get(){console.log('读取索引'+key,'当前值is'+val)returnval;},set(newVal){if(val===newVal){return;}val=newValconsole.log('修改索引'+key,'新值为'+val)}})}consttestArray=[1,2,3,4,5]testArray.forEach((c,index)=>{defineReactive(testArray,index,c)});testArray[0]=100;testArray[5]=600;您也可以单击此处打开示例以查看控制台输出。可以看出,Object.defineProperty无法监控超出范围的数组元素操作,但是可以监控范围内元素的重新赋值。那么为什么在Vue中,数组类型的数据不能通过下标赋值直接监听呢?答案是出于性能考虑。从上面的基本例子可以看出,如果对象和数组需要监听每一个属性和元素,实际上是用Object.defineProperty劫持了每一个属性或元素。对象监听键和数组,但是使用数字下标作为键,数组的数据量可能很大,所以Vue出于性能考虑不响应元素下标。作为补充,Vue在数组原型链上劫持了几个方法。对于push、pop、unshift这三个会导致添加新元素的方法,它会在内部获取新元素并进行响应式处理:constmethodsToPatch=['push','pop','shift','unshift','splice','sort','reverse']methodsToPatch.forEach(function(method){//缓存原始方法constoriginal=arrayProto[method]def(arrayMethods,method,functionmutator(...args){constresult=original.apply(this,args)constob=this.__ob__letinsertedswitch(method){case'push':case'unshift':inserted=argsbreakcase'splice':inserted=args.slice(2)中断}if(inserted)ob.observeArray(inserted)ob.dep.notify()returnresult})})类似对象,如果需要重新赋值,可以使用vm.$set(arr,indexOfItem,newValue)方法,这里是$set的具体实现://简化非生产逻辑导出函数set(target:Array|Object,key:any,val:any):any{if(Array.isArray(目标)&&isValidArrayIndex(键)){target.length=Math.max(target.length,key)target.splice(key,1,val)returnval}if(keyintarget&&!(keyinObject.prototype)){目标[key]=valreturnval}constob=(target:any).__ob__if(target._isVue||(ob&&ob.vmCount)){process.env.NODE_ENV!=='production'&&warn('避免向Vue实例或其根$data添加响应式属性'+'at运行时-在数据选项中预先声明它。')returnval}if(!ob){target[key]=valreturnval}defineReactive(ob.value,key,val)ob.dep.notify()returnval}主要逻辑包括以下操作:该方法同时用于对象和数组,所以第一步检查传入的目标是否为数组,传入的key(即数组的数字下标)是否符合到数组的长度范围(上面提到Object.defineProperty不支持劫持新增元素),匹配到的元素会调用splice插入到数组中。由于splice已经被劫持,新添加的元素将以“响应式”的方式进行处理。如果存在,则无需监控。判断是根节点Vue,也就是最外层的Vue,或者已经有__ob__属性(说明已经做了响应式处理,具体可以看上一篇),那么就不用了监视。如果不满足前面的条件,则表示该属性需要进行“响应式”处理,会调用defineReactive方法(响应式数据封装的入口方法,详见上一篇文章)。