effect
方法主要用于处理函数的响应式,可用于计算属性和watchEffect等功能,通过触发函数中响应式变量的proxy
的get
方法实现将自身加入到proxy
的deps
中,实现与proxy
关联,也可以将其他依赖收集到自己的deps中
1. effect
创建一个effect函数,如果不是lazy创建时会先调用一遍,这样可以调用相关get并将effect加入相关proxy的deps中
function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) { // 如果已经是effect则将原函数取出来
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) { // 如果不是lazy则先调用一遍effect,这个时候可以触发相关proxy的get
effect()
}
return effect
}
2. createReactiveEffect
当调用effect
时会创建一个函数,该函数主要用于收集依赖和充当watcher。当生成的函数被调用时,先判断active属性,如果false则不会被收集依赖;接着将自身添加到effectStack中和将activeEffect改为自身,当调用fn时触发fn中响应式变量的get,这时get会把effect添加到响应式变量的依赖中;最后从effectStack去除并重新赋值activeEffect。
function createReactiveEffect<T = any>(
fn: (...args: any[]) => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(...args: unknown[]): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn(...args)
}
if (!effectStack.includes(effect)) {
// 将effect从target.key的依赖中删除,并将自身的deps重置
// 这里每次都要清除一次,可能是为了防止fn中有条件判断导致响应式数据变化而导致依赖没有及时变化
cleanup(effect)
try {
// 将标记置为可收集依赖
enableTracking()
effectStack.push(effect)
// 将effect赋给activeEffect,会在reactive的getter方法中调用track放入reactive的deps中
activeEffect = effect
// 这里先调用一遍,触发getter
return fn(...args)
} finally {
// 最后要把处理好的effect从队列中去掉
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = [] // 这里放置和effect相关的deps
effect.options = options
return effect
}
3. track
这里主要用于收集依赖
function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
return
}
// 根据target将一个Map放入targetMap中
let depsMap = targetMap.get(target)
if (depsMap === void 0) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (dep === void 0) {
// 用Set存储deps
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
// 将effect添加到target.key的依赖中
dep.add(activeEffect)
// 将target.key的依赖收入到effect中,用于cleanup中使用
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}