vue中如何更新一个数组项,这是开发中经常遇到的问题例如:data中有一个list数组,数组export中有三个itemdefault{data(){return{list:['foo','bar','baz']}}}如何将第二项的值更新为'jerry'很多朋友可能试过this.list[1]='jerry',但遗憾的是该页面将不会更新。该方法确实改变了list[1]的值,但不能触发页面更新。那么在Vue中,如何更新数组中的一个item呢?以下是总结的一些方法:数组原生方法Array.prototype.splice被称为数组最强大的方法,具有删除、添加、替换功能,可以使用splice来更新this.list.splice(1,1,'jerry')为什么拼接可以触发更新?Vue包装了监听数组(在本例中为列表)的更改方法,因此它们也会触发视图更新。这些被包装的方法包括:push()pop()shift()unshift()splice()sort()reverse()splice不再是数组原生方法,而是Vue重写的方法部分源码: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)break}if(inserted)ob.observeArray(inserted)//通知更改ob.dep.notify()returnresult})})可以看到,splice除了会执行自己的逻辑(original.apply(this,args)),还会将插入的值变成响应式对象(observeArray(inserted)),然后Callob.dep.notify()手动触发ger依赖通知。详情src/core/observer/array.js官方APIVue.set()Vue.set是官方全局API,别名vm.$set,用于主动触发响应Vue.set(this.list,1,'jerry')//orthis.$set(this.list,1,'jerry')其实set方法本质上是调用splice方法触发响应,部分源码函数set(target:Array|Object,key:any,val:any):any{//...if(Array.isArray(target)&&isValidArrayIndex(key)){target.length=Math.max(target.length,key)目标。splice(key,1,val)returnval}//...}当set的第一个参数为数组时,直接调用target.splice()详情src/core/observer/index.jsvm.$forceUpdate()即可强制Vue实例重启Rendering,其实就是this.list[1]='jerry'操作,list确实发生了变化,我们调用vm.$forceUpdate()强制渲染this.list[1]='jerry'this.$forceUpdate()通常你应该避免这个方法,而是通过正常的数据驱动方法来操作当你有无处可去,你可以试试这个方法,但是这个方法不能滥用,认为你只是想改变一个数组项,但可能会更新整个组件正如官网所说:如果你发现自己需要做一个强制更新在Vue中,99.9%的时间,你在某个地方做错了。深拷贝一般是通过序列化和反序列化回来实现的。this.list[1]='jerry'this.list=JSON.parse(JSON.stringify(this.list))可能会自己封装cloneDeep方法,虽然也能触发响应,但是真的有点别扭使用深拷贝只是为了更新某个项目。会改变原来的数组)和slice、concat、filter等,它们不会改变原来的数组,而是总是返回一个新的数组,所以在vue中我们可以直接替换数组来实现updatethis.list=this.list.map((item,index)=>{if(index===1){return'jerry'}returnitem})你可能认为这会导致Vue丢弃现有的DOM并重新渲染整个列表。幸运的是,Vue做得绰绰有余。Vue实现了一些智能启发式算法以最大限度地重用DOM元素,因此用包含相同元素的数组替换原始数组是一种非常高效的操作。请记住,在模板中使用v-for时必须提供密钥。Vue可以根据这个键值更高效的找出差异,准确定位并更新。其实这种根据源数据生成新数据(不影响外界)的方式很符合函数式编程的思想。如果你用过redux,在写reducer的时候会经常把map和filter作为对象使用。在开发中,你可能会遇到this.list[1].name='jerry'。如果数组项是一个对象,那么可以直接通过下标更新这个对象的属性。实际上,当Vue对数组进行初始化处理后,无论数组项是否为对象,都会直接返回,不做任何操作。如果是对象,会用Observer类初始化,对象属性会加上getter和setterlistener部分源码:classObserver{value:any;部门:部门;vmCount:数量;//以该对象为根的虚拟机数量$dataconstructor(value:any){this.value=valuethis.dep=newDep()this.vmCount=0def(value,'__ob__',this)if(Array.isArray(value)){if(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}this.observeArray(value)}else{this.walk(value)}}walk(obj:对象){常量键=对象。keys(obj)for(leti=0;i){for(leti=0,l=items.length;i<l;i++){observe(items[i])}}}Observer类初始化时,如果是数组,一般会调用protoAugment(),observeArray()protoAugment会将value(array)的__proto__指向arrayMethods,这里重点关注observeArray,它会对每个数组项调用observe(),observe如下:exportfunctionobserve(value:any,asRootData:?boolean):void{if(!isObject(value)||valueinstanceofVNode){return}letob:Observer|voidif(hasOwn(value,'__ob__')&&value.__ob__instanceofObserver){ob=value.__ob__}elseif(shouldObserve&&!isServerRendering()&&(Array.isArray(value)||isPlainObject(value))&&Object.isExtensible(value)&&!value._isVue){ob=newObserver(value)}if(asRootData&&ob){ob.vmCount++}returnob}可以看到,如果数组项不是对象,就会直接返回;如果数组项是对象,会继续用Observer初始化对象,然后调用walk(),为每个属性调用defineReactive()。defineReactive会通过Object.defineProperty为属性添加getter和setter监听器,因此重新分配数组项会触发响应。详细src/core/observer/index.js关于为什么vue没有把arr[index]=val改成Responsive,网上有很多讨论,作者也有答案。一般来说,性能成本与获得的用户体验不成正比。arr[index]=val虽然没有响应式,但是也提供了官方的API来操作。作为一个框架,Vue已经做得够多了。当然,Vue3把Object.defineProperty换成了Proxy,这个问题就不存在了。