前言
 
  
   
   -  
    
      本文是继上次vue3源码系列之高清版reactive源码分析篇之后,有一力作了。 
    
-  
    
-  
    
 
 基础使用
 
       const { reactive, effect } = Vue
      const data = { name: 'lixx', age: 20 }
      const state = reactive(data)
      effect(() => {
        document.getElementById('app').innerHTML = `我叫${state.name}, 我今天${state.age}岁了`
      })
      setTimeout(() => {
        state.age++
      }, 5000)
 
  
  -  
   
     上述实例中 
    reactive将数据转换成了响应式,effect的存在是为了实现依赖追踪
-  
    
    effect在初期默认会执行一次,页面上显示的结果是我叫lixx, 我今天20岁了
-  
   
     间隔5s后,依赖更新,导致 
    effect重新执行. 此时页面上显示我叫lixx, 我今天21岁了
-  
   
     上述实例就是简单使用,也没有什么难度,这里就不过度的赘述了。但是通过上述的实例,我们引出了 
   
 
   -  
    
-  
    
-  
    
-  
    
      搞懂上述三个问题,Vue的· 
     依赖收集基本就ok了,接下来我们一一实现
手写实现
 
 effect实现
 
       // 表示当前激活的effect
      let activeEffect = null
      /**
       * @desc 实现effect
       */
      const effectImpl = (fn) => {
        const _effect = new ReactiveEffect(fn)
        // effect 函数初期执行一次
        _effect.run()
        return _effect
      }
      class ReactiveEffect {
        constructor(fn) {
          this.fn = fn
          this.active = true
          this.parent = null
          this.deps = []
        }
        run() {
          if (!this.active) return this.fn()
          try {
            // 将激活的effect 给父类
            this.parent = activeEffect
            activeEffect = this
            // 每次收集依赖之前 清空依赖
            cleanEffect(this)
            return this.fn()
          } finally {
            activeEffect = this.parent
            this.active = false
          }
        }
      }
 
  
  -  
   
     上述就是 
    effect基本实现。这里我们会挨个解释下。
-  
    
    effect是整个Vue的依赖收集基础。比如:watch,computed都是以effect为基础的
-  
   
     其实通过上述代码可以看出。 
    effect的参数是一个函数fn。而我们使用ReactiveEffect类进行包裹
-  
    
    ReactiveEffect内部相对来说比较简单,其实就是两个意思:
 
   -  
    
      将当前effect设置为激活的effect 
     activeEffect = this
-  
    
      调用effect函数 
     fn。通过调用函数fn就执行了document.getElementById('app').innerHTML = "我叫${state.name}, 我今天${state.age}岁了"了。开始执行proxy.get了
-  
   
     此时共同变量 
    activeEffect 就是此时运行的ReactiveEffect实例
依赖收集入口
 
       /**
       * @desc 实现reactive函数
       */
      const reactiveImpl = (target) => {
        if (!isObject(target)) {
          console.warn(`${target} need is object`)
          return target
        }
        const existProxy = proxyMap.get(target)
        if (existProxy) return existProxy
        if (target[ReactiveFlags.IS_REACTIVE]) return target
        const proxy = new Proxy(target, {
          get: (target, key, receiver) => {
            const res = Reflect.get(target, key, receiver)
            // 依赖收集
            track(target, TrackOpTypes.GET, key)
            return isObject(res) ? reactive(res) : res
          },
          set: (target, key, newValue, receiver) => {
            const res = Reflect.set(target, key, newValue, receiver)
            // 触发依赖
            trigger(target, TrackOpTypes.set, key, newValue)
            return res
          }
        })
        proxyMap.set(target, proxy)
        return proxy
      }
 
  
  -  
   
     理解上述代码可以将问题先简单化(假如只有一个函数以及一个属性): 
   
 
   -  
    
      当属性获取值的时候,会触发 
     proxy.get函数,此时我们将被调用函数赋值给一个公共的变量
-  
    
      当属性设置值的时候,会触发 
     proxy.set函数,此时我们调用共同变量(上一步被赋值过了)
-  
    
      上述就是简单原理实现。而Vue的 
     依赖收集无非是这个基础上进行扩展而已
-  
   
     言归正传,通过上述的 
    proxy实现。当属性获取值的时候,会触发proxy.get方法。从而触发依赖收集track(target, TrackOpTypes.GET, key)
-  
   
     通过代码 
    state.age++触发了proxy.set。从而执行了trigger(target, TrackOpTypes.set, key, newValue)
依赖收集
 
       /**
       * @desc 实现依赖收集
       */
      const track = (target, type, key) => {
        if (!activeEffect) return
        let depsMap = targetMap.get(target)
        if (!depsMap) {
          targetMap.set(target, (depsMap = new Map()))
        }
        let dep = depsMap.get(key)
        if (!dep) {
          depsMap.set(key, (dep = new Set()))
        }
        const shouldTrack = !dep.has(activeEffect)
        if (shouldTrack) {
          // 双向记忆方便清除
          dep.add(activeEffect)
          activeEffect.deps.push(dep)
        }
      }
 
  
  -  
   
 
   -  
    
      target => 元对象 key => 触发属性 activeEffect => 当前激活的effect 
    
-  
    
      WeakMap => Map => Set => activeEffect 
    
-  
    
      {target: [key] => {new Set([effect1, effect2])}} 
    
-  
    
      在外层的WeakMap中以元对象为key。值就是Map 
    
-  
    
      内层的Map就是以依赖的属性为key,值就是Set 
    
-  
    
      里层的Set中存放了多个effect。因为可能多个effect依赖相同的属性 
    
触发依赖
 
       /**
       * @desc 实现依赖触发
       */
      const trigger = (target, type, key, value) => {
        const depsMap = targetMap.get(target)
        if (!depsMap) return
        const deps = depsMap.get(key)
        if (!deps) return
        const effects = new Set(deps)
        for (const effect of effects) {
          effect.run()
        }
      }
 
  
  -  
   
     依赖触发就是将存储的effect找到。调用其中的 
    run函数。就重新执行了
源码对照
 
 effect实现
 
  
 
 
 export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 用类ReactiveEffect 实现effect
  const _effect = new ReactiveEffect(fn)
  if (!options || !options.lazy) {
    // 立刻执行 收集依赖
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}
 
 类ReactiveEffect实现
 
  
 
 
 /**
 * @description 使用类构造一个effect
 */
export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []
  parent: ReactiveEffect | undefined = undefined
  
  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope)
  }
  // 执行run函数
  run() {
    // 如果effect不是active状态 只执行函数 没有设置激活的effect
    if (!this.active) {
      return this.fn()
    }
    // 激活的effect给parent  第一个肯定是undefined
    let parent: ReactiveEffect | undefined = activeEffect
    try {
      // 上一次effect 赋值给parent
      this.parent = activeEffect
      // this表示当前激活的effect
      activeEffect = this
      // 执行函数
      return this.fn()
    } finally {
      // 将父类 赋值给effect
      activeEffect = this.parent
      this.parent = undefined
    }
  }
}
 
 依赖收集
 
  
 
 
 /**
 * 依赖收集函数
 */
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 如果当前effect 不激活 不进行处理
  if (shouldTrack && activeEffect) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep()))
    }
    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined
    trackEffects(dep, eventInfo)
  }
}
 
 触发依赖
 
  
 
 
  
  -  
   
     触发依赖的细节比较多。将源码赋值过来意义不大。这里就不复制了 
   
结束
 
  
   
   -  
    
      源码中所有的逻辑都不在一个文件中,所以看起来相对费劲点。但是小编写的demo是按照源码来的,可以称之为缩小版。连起名字都是模仿源码的。如果能理解小编手写的源码。再对照着给出的源码位置大体看下。基本就没有问题了。如果有什么问题???欢迎留言啊 
    
-  
    
      如果觉得整个分析对各位有帮助的话,欢迎点赞 + 收藏 + 关注一条龙哦 
    
 
 手写源码以及demo地址
 
  
   
   -  
    
-  
    
      https://gitee.com/li_haohao_1/vue-world/tree/master/vue3/effect