在代码块中我会添加注释,方便大家理解,配合vue文档看效果更佳 本篇针对 /packages/reactivity/src/effect.ts 与 /packages/reactivity/src/dep.ts
-------------- dep API -------------
createDep
//创建dep
export const createDep = (effects?: ReactiveEffect[]): Dep => {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0 // wasTracked
dep.n = 0 // newTracked
return dep
}
可以看到dep就是Set对象,并且该对象上有一个w标记,一个n标记。w用来表示以前是否被收集过,n表示是否是重新收集。注意这两标记用二进制表示,用于与trackOpBit做位运算。
wasTracked
//判断是否以前被收集过
export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
与trackOpBit与运算,例如: 0&10 = 0, 10&10 = 10, 10&100 = 0
newTracked
//判断是否是重新收集
export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0
与trackOpBit与运算,例如: 0&10 = 0, 10&10 = 10, 10&100 = 0
initDepMarkers
//初始化副作用所绑定的每个dep 的 w 标记
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].w |= trackOpBit //或运算,例如 0|10 = 10, 10|100 = 100
}
}
}
finalizeDepMarkers
//对当前副作用所有绑定的dep,进行对当前副作用的判断清理,以及标记回归
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
const { deps } = effect
if (deps.length) {
let ptr = 0
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
if (wasTracked(dep) && !newTracked(dep)) {
dep.delete(effect)
} else {
deps[ptr++] = dep
}
//源码中这里备注的是clear bits,我觉得用标记回归去理解会比较好
dep.w &= ~trackOpBit
dep.n &= ~trackOpBit
}
deps.length = ptr
}
}
遍历副作用中所有相关的dep,进行判断,如果以前被收集过且不是重新收集则清除。 标记回归:把w和n标记重置到effect执行前的状态。
-------------- effect API -------------
先介绍一下会用到的东西
//存储收集的依赖,使用WeakMap弱引用,当key失去引用时,可以被垃圾回收机制回收。
const targetMap = new WeakMap<any, KeyToDepMap>()
//当前递归跟踪的层数。
let effectTrackDepth = 0
// 当前递归跟踪的层数(二进制), 例如: 1, 10, 100 ...
export let trackOpBit = 1
// 最多支持 30 层递归
const maxMarkerBits = 30
//副作用栈,记录当前操作过程的所有的effect
const effectStack: ReactiveEffect[] = []
//当前副作用
let activeEffect: ReactiveEffect | undefined
//迭代key
export const ITERATE_KEY = Symbol(__DEV__ ? 'iterate' : '')
//Map迭代key
export const MAP_KEY_ITERATE_KEY = Symbol(__DEV__ ? 'Map key iterate' : '')
shouldTrack
let shouldTrack = true //代表能否进行依赖收集,默认为true
const trackStack: boolean[] = [] //记录shouldTrack的栈
//开启,压栈,并设置为true
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
//暂停,压栈,并设置为false
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
//重置收集,出栈,并设置为出栈结果
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last //不存在则设置为true
}
//是否可收集
export function isTracking() {
return shouldTrack && activeEffect !== undefined //当前处理中的副作用必须有效
}
ReactiveEffect
// 响应式副作用类声明
export class ReactiveEffect<T = any> {
active = true //活动状态,默认true
deps: Dep[] = [] // 存储dep的数组
computed?: boolean // computed?
allowRecurse?: boolean //允许递归?
onStop?: () => void
onTrack?: (event: DebuggerEvent) => void
onTrigger?: (event: DebuggerEvent) => void
constructor(
public fn: () => T, //副作用函数
public scheduler: EffectScheduler | null = null, //调度器, computed 和 watch 中被使用
scope?: EffectScope | null // 作用范围 render 中被使用
) {
recordEffectScope(this, scope) //记录副作用范围
}
run() {
//如果非活动状态
if (!this.active) {
return this.fn()
}
//判断是否在副作用栈,存在则跳过
if (!effectStack.includes(this)) {
try {
//把当前处理的副作用压入栈中
effectStack.push((activeEffect = this))
//开启收集
enableTracking()
//effectTrackDepth + 1 , trackOpBit进位, 例如:1<<1 = 10, 1<<2 = 100
trackOpBit = 1 << ++effectTrackDepth
//判断是否超过最大层数
if (effectTrackDepth <= maxMarkerBits) {
initDepMarkers(this) //初始化 dep.w 标记
} else {
cleanupEffect(this) //解除副作用与dep之间的绑定
}
return this.fn() //执行fn并返回
} finally {
//判断是否超过最大层数
if (effectTrackDepth <= maxMarkerBits) {
finalizeDepMarkers(this) //去除不相关依赖,回归 n,w 标记
}
//effectTrackDepth - 1 , trackOpBit退位, 例如:1<<2 = 100,1<<1 = 10,
trackOpBit = 1 << --effectTrackDepth
//重置收集依赖状态
resetTracking()
//当前副作用出栈
effectStack.pop()
//如果effectStack里还有副作用,则将activeEffect指向栈中顶部的副作用
const n = effectStack.length
activeEffect = n > 0 ? effectStack[n - 1] : undefined
}
}
}
//停止活动
stop() {
if (this.active) {
cleanupEffect(this) //清除相关依赖
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
run方法: 层级积累, 初始化标记, 跑fn, 标记回归, 层级回退
cleanupEffect
//解除副作用与dep之间的绑定
function cleanupEffect(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect) //执行delete,删除 dep 中当前的副作用
}
deps.length = 0 // 将副作用所关联的dep数组清空
}
}
遍历副作用中所有的dep,清楚绑定,并将deps清空。
effect
副作用函数
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
//如果fn曾经被effect过,则取出原来的fn替换之
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
const _effect = new ReactiveEffect(fn) //创建响应式副作用对象
//如果有配置信息
if (options) {
extend(_effect, options) //对_effect进行配置合并
if (options.scope) recordEffectScope(_effect, options.scope) //记录副作用范围
}
//如果是懒副作用则不立即执行
if (!options || !options.lazy) {
_effect.run()
}
//获取run方法,并包装对应的副作用
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
根据fn创建ReactiveEffect对象,非lazy则立即执行ReactiveEffect.run(),包装run并返回。
stop
结束副作用
export function stop(runner: ReactiveEffectRunner) {
runner.effect.stop()
}
就调用了ReactiveEffect的stop方法。上面有具体介绍。这里只是暴露该API方便使用。
track
export function track(target: object, type: TrackOpTypes, key: unknown) {
//当前处于不可收集状态则退出
if (!isTracking()) {
return
}
//获取target对应的dep,这里做了一些取值的逻辑处理
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)
}
收集,这个在baseHandlers与conllectionHandlers中被使用。这里主要是对targetMap进行一些取值的逻辑处理。 在执行trackEffects前,处理好target对应的dep,并传入。
trackEffects
收集副作用
export function trackEffects(
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
let shouldTrack = false //定义自己的shouldTrack
if (effectTrackDepth <= maxMarkerBits) {
//如果不是重新收集
if (!newTracked(dep)) {
dep.n |= trackOpBit // 设置n标记,例如:0|10 = 10, 10|100 = 110
shouldTrack = !wasTracked(dep) //以以前是否被收集过作为判读
}
} else {
//依赖收集层数超出边界则直接以dep是否含有当前副作用作为判断
shouldTrack = !dep.has(activeEffect!)
}
//如果需要收集,则进行 dep 与 副作用的双向关联
if (shouldTrack) {
dep.add(activeEffect!)
activeEffect!.deps.push(dep)
if (__DEV__ && activeEffect!.onTrack) {
activeEffect!.onTrack(
Object.assign(
{
effect: activeEffect!
},
debuggerEventExtraInfo
)
)
}
}
}
首先判断是不是重新的收集,不是则记录n标记,并判断以前是否被收集过来给shouldTrack赋值。如果需要收集则进行双向关联。dep.add(activeEffect!),触发依赖时会遍历dep执行每个副作用。activeEffect!.deps.push(dep),用于 cleanupEffect, finalizeDepMarkers。
trigger
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
//从未被收集
return
}
let deps: (Dep | undefined)[] = []
//清空操作
if (type === TriggerOpTypes.CLEAR) {
//取出target中所有key对应的dep
deps = [...depsMap.values()]
//target为数组且改变的是数组长度
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
//取出key和大于长度新值下标的dep
if (key === 'length' || key >= (newValue as number)) {
deps.push(dep)
}
})
} else {
// key 不等于 undefined
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// ADD | DELETE | SET
switch (type) {
case TriggerOpTypes.ADD:
//不是数组则加入对应迭代key对应的dep
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
//是数组则加入length对应的dep
} else if (isIntegerKey(key)) {
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
//不是数组则加入对应迭代key对应的dep
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
//加入迭代key对应的dep
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
const eventInfo = __DEV__
? { target, type, key, newValue, oldValue, oldTarget }
: undefined
//如果只有一个,则直接调用triggerEffects
if (deps.length === 1) {
if (deps[0]) {
if (__DEV__) {
triggerEffects(deps[0], eventInfo)
} else {
triggerEffects(deps[0])
}
}
} else {
//effects:收集所有dep中的effect
const effects: ReactiveEffect[] = []
for (const dep of deps) {
if (dep) {
effects.push(...dep)
}
}
//使用createDep()处理effects返回一个dep,调用triggerEffects触发
if (__DEV__) {
triggerEffects(createDep(effects), eventInfo)
} else {
triggerEffects(createDep(effects))
}
}
}
首先判断target是否被收集过,如果没有则直接退出。
声明一个deps用来存储所有需要触发的dep。
1)
如果是TriggerOpTypes.CLEAR,则添加所有dep,如调Set或者Map的clear方法。
2)
如果是数组且是更改length,则添加length以及所有大于length下标的dep。
如 arr = [1,2,3], arr.length = 2,则添加length以及3对应的dep。
3)
key不等于undefined,添加其对应的dep。
判断触发操作类型 ADD | DELETE | SET
ADD:给普通对象或数组新增属性 或 Set.add, WeakSet.add
判断是不是数组,加入迭代key对应的dep或数组长度对应的dep。
DELETE: delete操作符 或 Map.delete, Set.delete, WeakMap.delete, WeakSet.delete
不是数组加入迭代key对应的dep。
SET: 修改普通对象或数组属性 或 Map.set, WeakMap.set
加入迭代key对应的dep。
4)
如果dep只有一个则直接触发。
否则合并成一个dep,在进行触发。
triggerEffects
export function triggerEffects(
dep: Dep | ReactiveEffect[],
debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
// 遍历所有effect
for (const effect of isArray(dep) ? dep : [...dep]) {
//如果不是当前副作用 或者 允许递归
if (effect !== activeEffect || effect.allowRecurse) {
if (__DEV__ && effect.onTrigger) {
effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
}
//有调度器则触发,否则run
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}