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进行添加到执行队列中(简化版)