《Vuejs设计与实现》5.8.3 避免污染原始数据

127 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情

上文中,我们了解到了如何处理Set的响应式,那么你照着书本去实现有一下Map的响应式应该也不难.今天我们看一下Map的响应式中碰见的一个问题。

在我们之前封装的set方法中,有个重要的问题:它会污染原始数据。我们看一下下面这个demo.

const origin = new Map()
const p = reactive(origin)
const p2 = reactive(new Map())
p1.set('p2',p2)
effect(()=>{
    console.log(origin.get('p2').size)
})
origin.get('p2').set('a',1)

梳理一下上面的逻辑

  • 首先创建一下原始值Map
  • 创建了一个响应式的p
  • 创建一个响应式的p2
  • 给p中新增一个key为p2,值是p2
  • 监听原始值中p2的变化
  • 修改原始值中p2的值

我们会发现这段代码有一个很明显的问题,就是取值的时候用的是原始对象,而不是响应式对象。虽然我那篇文章中没有具体的写,但是[[Set]]的时候肯定也会更新原始值对象。

image.png

而按道理说,我们更新的是原始值,并不会执行副作用函数,但是由于我们在代理的时候污染了原始值,原始值中存在了响应式的值,就会导致副作用的执行,在我看来,这是不对的。

找到了问题,解决起来就不难了,当我们在[[Set]]的时候,我们判断一下它是不是原始值,甚至我们都不需要判断,我们直接使用target.raw即可。

const methods ={
 set(key,value){
     /*省略其他逻辑*/
     const target = this.raw
     target.set(key,value.raw || value)
 }
}

这样,当我们设置原始值时就不会带入响应式的值,从而避免了污染。但是这里面其实还有一些小问题,比如我们把原始值的key为raw,有可能和用户的键值冲突,这里最好用一个Symbol代替

这里除了set之外,像Set.add也要做类似的处理