Vue3 reactive api 处理对象

221 阅读2分钟
  1. in 操作, 对应 proxy 是 has 方法,

    const obj = { foo: 1 }
    const p = new Proxy(obj, {
        has(target, key) {
            // has 方法进行track
            track(target, key)
            return Reflect.has(target, key)
        },
    })
    effect(() => {
        foo in obj // 通过has方法建立依赖关系
    })
    
  2. 对象 for in 操作,for in 的内部方法对应 proxy 来说就是 ownKeys, 但是对 for in 进行 track 时,没有具体的 key,需要手动指定一个 key

    1. 新增一个属性 for in 会变化,操作类型是 ADD 需要在 proxy set 方法里面判断这个属性是新增还是修改

    2. 删除一个属性 for in 会变化,操作类似是 DELETE,需要通过 proxy deleteProperty 进行特别处理

    相当于 set deleteProperty 都需要处理两个依赖,而不是专属于 key 的依赖

    let ITERATE_KEY = symbol()
    const proxy = new Proxy(
        { foo: 1 },
        {
            set(target,key,newVal,receiver) {
              const type = Object.prototype.hasOwnProperty.call(target,key) ? 'SET' : 'ADD'
              let res = Reflect.set(target,key,newVal,receiver)
              trigger(target,key,type)
              return res
            }
            ownKeys(target) {
              // ownKeys 没有具体的key,需要在track时 手动 key 为 ITERATE_KEY
                track(target, ITERATE_KEY)
                return Reflect.ownKeys(target)
            },
            deleteProperty(target,key) {
    
              const res = Reflect.deleteProperty(target,key)
              const hadKey = Object.prototype.hasOwnProperty.call(target,key)
    
              if (res && hasKey) {
                // 只有当删除一个已存在的属性 && 删除成功时,才需要trigger
                trigger(target, key, 'DELETE')
              }
            }
        }
    )
    effect(()=>{
      for (let key in proxy) {}
    })
    proxy.count = 1 // effect 需要重新执行, 但是trigger拿到的key是count,只会触发count收集到的依赖,所以在添加属性时,要将ITERATE_KEY收集到的副作用函数重新执行
    function trigger(target,key,type) {
    
      const depsMap = bucket.get(target)
      const effectsToRun = new Set()
      /**
       * 操作类似是ADD时, trigger 专门获取 target ITERATE_KEY收集的依赖,然后触发
       * 操作类型是 DELETE时,也需要特别处理
       */
      if (type === 'ADD' || type === 'DELETE') {
         const iterateEffects = depsMap.get(ITERATE_KEY)
           iterateEffects && iterateEffects.forEach(effect => {
             if (effect !== activeEffect) {
               effectsToRun.add(effect)
             }
           })
      }
    
    }
    
  3. 修改原型链上的属性,导致副作用函数触发多次

    const child = reactive({})
    const parent = reactive({ count: 1 })
    Object.setPropertyOf(child, parent) // 继承 parent
    
    effect(() => {
        /**
         * child.count 收集了副作用函数
         * parent.count 也收集了副作用函数
         * 根据原型链的查找规则,访问了child.count 收集一次依赖,child没有count属性,沿着原型链查找 到parent,parent也是响应式的,有count属性,所以也会访问 parent.count 属性
         */
        console.log(child.count)
    })
    child.count = 2 // 副作用函数被执行了两次,同上面的道理 child.set & parent.set 都触发了
    
    // 是 set 导致 trigger触发了两次,需要在 set 里面屏蔽一次不必要的trigger就可以了
    
    get(target,key) {
      /**
       * 响应式对象通过 raw 获取原始对象
       */
      if (key === 'raw') {
        return target
      }
    }
    set(target,key,newVal,receiver) {
      /**
       * chiil.count -> reciver = child
       * 隐式的 parent.count -> receiver = child
       * 两次receiver 都是 child
       * 只有代理对象的原始对象 = target时 才触发trigger
       */
      if (receiver.raw === target) {
        trigger(target,key)
      }
    
    }