vue3源码effect

Effect

前言:effect类似于vue2源码中的watch,观察者/订阅者。
以下过程中,effect为观察者,target的属性值为被观察者,effect观察target的属性值,target的属性值被修改通知effect,确保以这个思想看待下面源码。

Effect与Target映射

target ===> effect

// 存放effect集合,使用Set去重
type Dep = Set<ReactiveEffect>
// any为监控对象的属性,属性对应的Dep
// 存放监控对象所有属性与对应Dep
type KeyToDepMap = Map<any, Dep>
// any为target
const targetMap = new WeakMap<any, KeyToDepMap>()

上述代码目的:使用Proxy监控是target,每次触发handler都是属性维度,因此需要维护属性与effect集合的映射。

Dep == 》effect集合

keyToDepMap ===》 target的所有属性对应Dep

targetMap ===》target对应的所有属性监控

修改target对应属性,可以通过以上映射找到该属性对应的effect进行触发执行。

effect的类型

export interface ReactiveEffect<T = any> {
  (): T
  // effect的标志属性
  _isEffect: true
  // 唯一标识
  id: number
  active: boolean
  // 原始函数
  raw: () => T
  // 关联effect的响应数据的观察者集合
  // effect与对应的响应数据指向的观察者集合是同一个对象
  // 目的就是为了当该effect需要与响应数据分开时,直接从deps中移除该effect,同时对应的响应数据的观察者集合也移除了该effect,因为指向同一份数据
  deps: Array<Dep>
  // effect的配置选项
  options: ReactiveEffectOptions
}
// active属性作用是当为true时,会清楚effect的依赖并且执行onStop事件
// stop会在unmountComponent内部进行触发
// 因此是组件消亡的时候会触发stop,如果active为true会清除依赖触发onStrop
stop(effect: ReactiveEffect) {
  if (effect.active) {
    cleanup(effect)
    if (effect.options.onStop) {
      effect.options.onStop()
    }
    effect.active = false
  }
}

大家可以从后面的effect创建函数里面了解到,active为false时,effect就是一个普通的传入函数或undefined,没有监听的依赖项

effect的配置属性

export interface ReactiveEffectOptions {
  // 是否立即执行
  lazy?: boolean
  // 调度器,是否自定义调度effect
  scheduler?: (job: ReactiveEffect) => void
  // 是否收集的监听函数
  onTrack?: (event: DebuggerEvent) => void
  // 是否触发的监听函数
  onTrigger?: (event: DebuggerEvent) => void
  // 是否停止的监听函数
  onStop?: () => void
}
// 事件类型
export type DebuggerEvent = {
  // effect
  effect: ReactiveEffect
  // 监听对象
  target: object
  // 类型,这个主要是看何种方式,例如ADD、DELETE、CLEAR
  type: TrackOpTypes | TriggerOpTypes
  // porxy监听的属性
  key: any
} & DebuggerEventExtraInfo
export interface DebuggerEventExtraInfo {
  newValue?: any
  oldValue?: any
  oldTarget?: Map<any, any> | Set<any>
}

effect构造函数

export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // 如果fn是监听函数,则获取原始函数,重新创建
  // 从这可以看出,effect每次会返回一个新的监控函数
  if (isEffect(fn)) {
    fn = fn.raw
  }
  // 核心就是createReactiveEffect
  const effect = createReactiveEffect(fn, options)
  // lazy属性就是标识是否默认执行一次
  if (!options.lazy) {
    effect()
  }
  return effect
}

传入一个函数,返回一个effect的构造函数

3.createReactiveEffect函数

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  // 创建ReactiveEffect类型的函数
  const effect = function reactiveEffect(): unknown {
    // 如果active为false,则不会进行依赖收集
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    // effectStack指的是effect栈
    // 如果effectStack中没有effect,则进行执行
	// 为了防止循环依赖
	// effect1中触发effect2,此时触发effect2的调用,此时effect2中又触发effect1,但是此时effect1还在effectStack中,则不会进入
    if (!effectStack.includes(effect)) {
      // 清除effect中对应的监控数据
      // 并且从监控数据依赖中清除该effect观察者
      // 双向进行清除,主要通过deps属性
      cleanup(effect)
      try {
        // 向trackStack推入一个shouldTrack
        // shouldTrack主要是是否进行依赖收集标志位
        enableTracking()
        // 向effectStack栈推入一个effect
        effectStack.push(effect)
        // 活动effect指向该effect
        activeEffect = effect
        // 执行fn,进行触发内部监控数据对应的getter来收集该effect依赖
        return fn()
      } finally {
        // 弹出该effect
        effectStack.pop()
        // 弹出shouldTrack
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  // 设置effect的基础属性
  effect.id = uid++
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}
// 记录上一次的shouldTrack状态
// 并且现在置为false
// 例如当wraning时会执行该函数
export function pauseTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = false
}
// 记录上一次的shouldTrack状态
// 并且现在依赖可进行收集,因为要执行fn,获取
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}
// effect执行完毕,则将shoudTrack置为之前状态
export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}

将原始函数fn重新定义为effect,主要作用类似watch当作为观察者

cleanup函数

作用:为了清除该effect依赖项

function cleanup(effect: ReactiveEffect) {
  // 获取与该effect有关的被观察者的effect集合
  // 从前面可知,该effect关联的监控对象指向的dep是在同一个存储地址
  const { deps } = effect
  if (deps.length) {
    // 从dep中删除该effect
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    // 并且去除deps的每一项
    deps.length = 0
  }
}

听讲解还是很懵逼?一张图搞定。
在这里插入图片描述

是不是非常清晰,deps[i].delete操作切除了被观察与effect的联系,deps.length=0,操作切除了effect与被观察的联系。

track函数

作用:依赖收集

export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 如果不进行依赖收集或目标观察者为undefined则直接返回
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  // 获取该target的属性与观察者收集箱集合
  // depsMap<key, dep>,key为tarfet属性
  let depsMap = targetMap.get(target)
  // 如果不存在则重新创建
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // 获取该key对应的依赖集合
  let dep = depsMap.get(key)
  // 如果不存在则重新创建
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  // 判断是否存在该effect观察者
  if (!dep.has(activeEffect)) {
    // 对应属性值的dep添加该effect
    dep.add(activeEffect)
    // 并且该effect中的deps也添加被观察的属性值的dep
    activeEffect.deps.push(dep)
    if (__DEV__ && activeEffect.options.onTrack) {
      // 触发onTrack函数
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}

很简单,主要是收集目标targetEffect,并且该targetEffect的deps也添加被观察属性值对应的dep,做双映射

trigget函数

作用:通知对应依赖执行回调函数

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  // 获取与target有关的所有观察者
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  // 添加一个依赖集合变量,Set确保唯一性
  const effects = new Set<ReactiveEffect>()
  // 从名字看出来添加函数
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        // 避免循环依赖
        if (effect !== activeEffect) {
          effects.add(effect)
        }
      })
    }
  }
  // clear指的是target清除所有属性触发的事件
  // 类似target = null
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 遍历所有属性的观察集合,添加到effects中
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    // 监控数组中length的长度变化,pop、shift、push、unshift等都会造成length变化
    depsMap.forEach((dep, key) => {
	  // 将属性length与key大于等于length的属性,全部添加到effects中
	  // 小于length的key自动进行依赖收集了,在这不需要收集
	  // 这里主要针对当直接对Array.length进行赋值时,要收集大于等于length的所有属性的依赖
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
	// 针对对应属性的dep进行添加activeEffect
    if (key !== void 0) {
      add(depsMap.get(key))
    }
	// ITERATE_KEY的收集是以下函数,主要是proxy中handler的第五个参数,监控对象属性集合的获取,例如Object.keys、for(key in target)等
	// function ownKeys(target: object): (string | number | symbol)[] {
  	// 	track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
  	// 	return Reflect.ownKeys(target)
	// }
    // also run for iteration key on ADD | DELETE | Map.SET
	// 删除添加都会改变对象与数组的keys长度与length
	// 举个例子,使用watch深层次监控target时,会进行key in target进行递归监控,因此会触发ownKeys,当target进行删除与添加属性时,同样会进行监控到
	// 可能会有其它作用,待使用后继续补充
    const isAddOrDelete =
      type === TriggerOpTypes.ADD ||
      (type === TriggerOpTypes.DELETE && !isArray(target))
    if (
      isAddOrDelete ||
      (type === TriggerOpTypes.SET && target instanceof Map)
    ) {
      add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))
    }
    if (isAddOrDelete && target instanceof Map) {
      add(depsMap.get(MAP_KEY_ITERATE_KEY))
    }
  }

  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
	// 如果存在scheduler,则运行scheduler调度器
	// 否则直接执行effect
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}

总结

effect相当于watch,target的属性与effect有一个映射表KeyToDepMap
target的属性值获取会触发track,此时会收集activeEffect
target的属性值修改会触发trigger,此时利用target从targetMap中获取与该对象有关的KeyToDepMap,然后使用属性key从KeyToDepMap获取dep,遍历dep进行添加到执行队列中(简化版)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值