Vue3中ref数组去重后出现Proxy(Object)是什么原因?

记得有一次,在一个Vue3 项目中,我使用Reference来装饰一个包含多个对象数组的数组,我想使用Set来去除重复。
结果看到控制台输出的是agent(object)。
这让我很困惑,因为我认为修改引用后使用 Set 删除重复项不会有问题。
后来网上查了一下,发现Vue3 引用返回的是响应式代理对象,而不是直接数据。
即使数据相同,该代理对象也被视为集合中的唯一个体。
我使用了Vue3 提供的toRaw方法。
它就像一把钥匙,可以打开客户的盒子并看到里面的原始数据。
我尝试将原始数组转换为字符串,然后对其进行重复数据删除,它确实有效!然而,这个过程非常慢,尤其是在处理大型数组时。
当时我突然想到,如果直接写一个去重功能,避免JSON处理,性能可能会更好。
不过这还是很复杂的,你要根据实际情况选择合适的方法。
您认为,有没有更快、更简单的方法来做到这一点?

Vue3 数组去重后出现 Proxy(Object) 数据的原因是什么?

说实话,这件事在Vue3 中是比较混乱的。
之前踩到陷阱的时候我也是一头雾水。
你提到的几点都是非常准确的。
让我补充一些个人的陷阱经验,以帮助您更清楚地弄清楚。

比如第一点,使用ref直接去重肯定是一个雷区。
我之前在组件中这样做过,但是去重后,又多了N个空的Proxies。
最后查了半天,发现包裹没有拆封。
我当时就怒了,因为obj.value.list明明是响应式的,怎么就变成了一堆Proxy呢? 查看源码后发现,Vue3 的ref会自动将普通对象包装成Proxy,而Set是比较引用,所以即使内容相同,它也认为是不同的。
在这些解决方案中,使用toRaw确实是最直接的,但是有一个陷阱需要注意:虽然toRaw可以去除重复,但是如果使用JSON转换字符串然后再转换回来,响应能力就会丢失。
后来改用Map结构去重,最后转回数组,这样响应能力可以保留,但性能上略有损失。

第二点其实和JavaScript的基础知识有关,但是在Vue3 中表现得更加明显。
记得有同学问我为什么他的数组去重后仍然有一堆重复的对象。
我让他打印出来,发现每个对象都是通过new Object()创建的。
虽然属性相同,但是内存地址却完全不同。
直接使用Set是肯定不行的。
我向他演示了将map转换为字符串,然后设置去重,最后解析回来的方法。
他听后说,太神奇了。
但请注意,如果对象中存在循环引用,JSON.stringify 会报错。
在这种场景下,必须使用更复杂的深拷贝算法。

最神奇的是第三点,重新分配时不能逐项修改。
我有一个项目,我使用 v-for 来呈现列表。
去重后,我直接obj.value.list[i] = newValue,但是结果页面根本没有更新。
后来我发现Vue3 的响应式依赖集合是基于数组索引的。
如果更改某个项目,则不会触发其他项目的依赖关系。
因此,必须将value整体赋值,如obj.value.list = newUniqueList,这样才能触发整个数组的更新。
一个小技巧是先用temp变量存储去重结果,最后赋值,避免赋值过程中触发冗余更新。

说实话,Vue3 的响应式系统确实比2 .x复杂得多,尤其是与原生JavaScript API结合时。
比如这次去重,其实用库函数更简单,比如lodash的uniqWith,可以直接处理响应式数据,但是在写业务代码的时候,往往要自己实现。
后来我总结了一个经验:所有涉及响应式数据的原生API操作都必须先toRaw,处理完后再使用ref.value赋值回来。
这样逻辑就清晰了,也容易出错。