Vue.js 3设计与实现(6)支持动态的副作用函数

2025/08/29
  // 上面的副作用函数时写死的,如何动态添加副作用函数
  {
    let data = { text: '文字块' }

    let activeEffect
    const effect = (fn) => {
      activeEffect = fn
      fn()
    }
    const bucket = new Set()

    const obj = new Proxy(data, {
      get(target, key) {
        activeEffect && bucket.add(activeEffect)
        return target[key]
      },
      set(target, key, val) {
        target[key] = val
        bucket.forEach(c => c())
        return true
      }
    })


    effect(() => {
      let a = document.createElement('li')
      a.innerText = obj.text
      a.style.cssText = 'background:green;border-radius:4px;margin:3px'
      document.body.appendChild(a)
    })

    setTimeout(() => {
      obj.text = '文字块2'
      obj.noExist = '111'    // 也触发了重新执行,原对象中没有这个属性
    }, 1000)

  }

  console.log('问题: 添加对象设置别的key也会触发响应');
  log('-----------------------------------------重新设计桶模型');

  // target -- key -- effectFn
  {
    const bucket = new WeakMap()

    const data = { text: 'bucket 2.0' }
    const obj = new Proxy(data, {
      get(target, key, val) {
        if (!activeEffect) {
          return target[key]
        }
        if (!bucket.get(target)) {
          bucket.set(target, new Map())
        }
        let depsMap = bucket.get(target)
        if (!depsMap.get(key)) {
          depsMap.set(key, new Set())
        }
        let deps = depsMap.get(key)
        deps.add(activeEffect)
        return target[key]
      },
      set(target, key, val) {
        target[key] = val
        const depsMap = bucket.get(target)
        if (!depsMap) return;
        const effects = depsMap.get(key)
        effects && effects.forEach(v => v())
        return true
      }
    })
    let activeEffect;
    const effect = (fn) => {
      activeEffect = fn
      fn()
    }
    effect(() => {
      let a = document.createElement('li')
      a.innerText = obj.text
      a.style.cssText = 'background:green;border-radius:4px;margin:3px'
      document.body.appendChild(a)
    })
    setTimeout(() => {

      obj.text = 'bucket 2.0--'
      obj.noExist = '111' // 不会再触发,因为depsMap中找不到noExist对应的副作用集合
    }, 1000)

  }

  log('---------------------------------------函数封装、分支切换和cleanup');

  {
    let activeEffect
    const bucket = new WeakMap
    const effect = (fn) => {
      const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        fn()
      }
      effectFn.deps = []
      effectFn()
    }
    const track = (target, key) => {
      if (!activeEffect) {
        return target[key]
      }

      let depsMap = bucket.get(target)
      if (!depsMap) {
        bucket.set(target, (depsMap = new Map()))
      }
      let deps = depsMap.get(key)
      if (!deps) {
        depsMap.set(key, (deps = new Set()))
      }
      deps.add(activeEffect)
      activeEffect.deps.push(deps)
    }

    const trigger = (target, key, val) => {
      const depsMap = bucket.get(target)
      if (!depsMap) return;
      const effects = depsMap.get(key)
      /*
        此处如果遍历effects,会导致死循环
        let s = new Set([1])
        s.forEach(()=>{
          // cleanup操作
          s.delete(1)    
          // 触发getter 后的 track操作
          s.add(1)
          为避免这个情况,可基于原数据单独定义新Set,遍历这个Set
        })
      
      */

      const effectsToRun = new Set(effects)
      effectsToRun.forEach(v => v())
    }
    let data = { text: 'cleanup', ok: 1, foo: 1, bar: 1 }
    const obj = new Proxy(data, {
      get(target, key, val) {
        track(target, key)
        return target[key]
      },
      set(target, key, val) {
        target[key] = val
        trigger(target, key, val)
        return true
      }
    })

    const cleanup = (effectFn) => {
      for (let deps of effectFn.deps) {
        deps.delete(effectFn)
      }
      effectFn.deps.length = 0
    }

    effect(() => {
      let a = document.createElement('li')
      a.innerText = obj.ok ? obj.text : 'not'
      a.style.cssText = 'background:green;border-radius:4px;margin:3px'
      document.body.appendChild(a)
    })
    /*
    {
        {ok:true,text:'cleanup'}: {
          ok: [effectFn],
          text:[]
        }
    }
    */

    obj.ok = false
    setTimeout(() => {
      obj.text = 'cleanup2'
    }, 1000)
    let tmp;



  }


  log('---------------------------------------嵌套的effect与effect栈');
  /*
  在Vue内部,组件的渲染函数就是在一个effect中执行的
  那么,当一个组件渲染了另外一个组件,例如
  const Bar = {
    render(){}
  }
  const Foo = {
    render(){
      return <Bar />
    }
  }
  对应的effect函数调用是
  effect(() => {
    Foo().render()
    effect(() => {
      Bar().render()
    }
  })

  effect(() => {
    console.log('effect1执行');
    effect(() => {
      console.log('effect2执行');
      obj.bar
    })
    obj.foo
  })
  无论修改obj.foo还是obj.bar  打印的都是 effect2执行
  这是因为我们的activeEffect 全局变量只有一个,而嵌套函数,最内层函数最后执行,那么activeEffect永远指向的
  都是内层的副作用函数。最终形成的 对应关系如下
        bar --》  [内层effect]
  obj 
        foo --》  [内层effect]

  因为effect是嵌套的,自然我们想到了用栈来记录,每一级的副作用函数
  
  */
  {
    let activeEffect;
    const effectStack = []

    const bucket = new WeakMap()
    const effect = (fn) => {
      const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        effectStack.push(effectFn)
        fn()
        effectStack.pop()
        activeEffect = effectStack[effectStack.length - 1]
      }
      // 初始化deps是空数组,依赖这个副作用的所有变量的deps
      effectFn.deps = []
      effectFn()
    }

    const track = (target, key) => {
      if (!activeEffect) {
        return target[key]
      }

      let depsMap = bucket.get(target)
      if (!depsMap) {
        bucket.set(target, (depsMap = new Map()))
      }
      let deps = depsMap.get(key)
      if (!deps) {
        depsMap.set(key, (deps = new Set()))
      }
      deps.add(activeEffect)
      activeEffect.deps.push(deps)
    }

    const trigger = (target, key, val) => {
      const depsMap = bucket.get(target)
      if (!depsMap) return;
      const effects = depsMap.get(key)
      const effectsToRun = new Set()
      effects && effects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
          effectsToRun.add(effectFn)
        }
      })
      effectsToRun.forEach(v => v())
    }
    let data = { text: 'cleanup', ok: 1, foo: 1, bar: 1 }
    const obj = new Proxy(data, {
      get(target, key, val) {
        track(target, key)
        return target[key]
      },
      set(target, key, val) {
        target[key] = val
        trigger(target, key, val)
        return true
      }
    })

    // 在当前effectFn依赖的 副作用Set列表中,移除当前的effectFn
    const cleanup = (effectFn) => {
      for (let deps of effectFn.deps) {
        deps.delete(effectFn)
      }
      effectFn.deps.length = 0
    }

    // effect(() => {
    //   console.log('2--effect1执行');
    //   effect(() => {
    //     console.log('2--effect2执行');
    //     obj.bar
    //   })
    //   obj.foo
    // })
    // obj.foo = 3

    // obj.bar = 3
    console.log(bucket);

    effect(() => {
      console.log(123);

      obj.bar++
      /**
      相当于 obj.bar = obj.bar + 1,初始化时 在effectFn中fn读取bar触发track方法后,触发trigger,因为当前副作用函数
      还在执行,又再次触发了 副作用的执行,导致effectFn递归调用自己,产生栈溢出

      添加判断,如果当前
      */

    })

    obj.bar = 6
  }

  log('---------------------------------------调度执行');
  log('---------------------------------------computed与watch的实现');


</script>

Post Directory