effect 为 reactive 的核心功能实现,它接收数据变化后的执行函数,官方称为副作用函数
ReactiveEffect 称为副作用对象
effect我个人认为不可以直接单独说,它与另外一个类息息相关,你中有我,我中有你,它就是-dep
首先我们先看下他们的关系
ReactiveEffect 和 Dep关系与类型定义 代码是他们的TS类型定义代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 export type Dep = Set <ReactiveEffect > & TrackedMarkers type TrackedMarkers = { w : number n : number } export class ReactiveEffect <T = any > { deps : Dep [] = [] }
从类型定义上来看,可以看出。他们两个的关系非常密切。
effect 函数接受一个副作用函数,生成一个副作用对象,也就是说,当响应式数据发生变化时,这个副作用函数会被调用,从而更新相关的视图。 而 dep 则是一个依赖追踪系统,用于追踪响应式数据被哪些副作用对象所依赖。在 effect 函数内部,当响应式数据被访问时,dep 会记录当前处于激活的副作用对象,并将这个副作用对象加入到依赖列表中。当响应式数据被修改时,dep 会遍历依赖列表,将所有依赖中的副作用对象上的副作用函数全部重新执行一遍,从而更新相关的视图。 因此,可以说 effect 和 dep 是密不可分的。effect 创建的副作用对象会依赖某些响应式数据,而这些响应式数据被修改时,就需要通过 dep 来通知调用相关的副作用对象上的副作用函数进行更新。effect 和 dep 的配合使用,是 Vue3 实现响应式更新的重要手段。
整体代码 ReactiveEffect的逻辑 都在代码里面了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 export interface DebuggerOptions { onTrack?: (event: DebuggerEvent ) => void onTrigger?: (event: DebuggerEvent ) => void } export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void } export interface ReactiveEffectRunner <T = any > { (): T effect : ReactiveEffect } type KeyToDepMap = Map <any , Dep >const targetMap = new WeakMap <any , KeyToDepMap >()let effectTrackDepth = 0 export let trackOpBit = 1 const maxMarkerBits = 30 */ export function effect<T = any >( fn : () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { if ((fn as ReactiveEffectRunner ).effect ) { fn = (fn as ReactiveEffectRunner ).effect .fn } const _effect = new ReactiveEffect (fn) if (options) { extend (_effect, options) if (options.scope ) recordEffectScope (_effect, options.scope ) } if (!options || !options.lazy ) { _effect.run () } const runner = _effect.run .bind (_effect) as ReactiveEffectRunner runner.effect = _effect return runner } export class ReactiveEffect <T = any > { active = true deps : Dep [] = [] parent : ReactiveEffect | undefined = undefined computed?: ComputedRefImpl <T> allowRecurse?: boolean private deferStop?: boolean onStop?: () => void onTrack?: (event: DebuggerEvent ) => void onTrigger?: (event: DebuggerEvent ) => void constructor ( public fn: () => T, public scheduler: EffectScheduler | null = null , scope?: EffectScope ) { recordEffectScope (this , scope) } run ( ) { if (!this .active ) { return this .fn () } let parent : ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack while (parent) { if (parent === this ) { return } parent = parent.parent } try { this .parent = activeEffect activeEffect = this shouldTrack = true trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { initDepMarkers (this ) } else { cleanupEffect (this ) } return this .fn () } finally { if (effectTrackDepth <= maxMarkerBits) { finalizeDepMarkers (this ) } trackOpBit = 1 << --effectTrackDepth activeEffect = this .parent shouldTrack = lastShouldTrack this .parent = undefined if (this .deferStop ) { this .stop () } } } stop ( ) { if (activeEffect === this ) { this .deferStop = true } else if (this .active ) { cleanupEffect (this ) if (this .onStop ) { this .onStop () } this .active = false } } } function cleanupEffect (effect: ReactiveEffect ) { const {deps} = effect if (deps.length ) { for (let i = 0 ; i < deps.length ; i++) { deps[i].delete (effect) } deps.length = 0 } }
其中dep 收集当前副作用对象时,涉及到一些dep的操作函数在这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 export const wasTracked = (dep : Dep ): boolean => (dep.w & trackOpBit) > 0 export const newTracked = (dep : Dep ): boolean => (dep.n & trackOpBit) > 0 export const initDepMarkers = ({ deps }: ReactiveEffect ) => { if (deps.length ) { for (let i = 0 ; i < deps.length ; i++) { deps[i].w |= trackOpBit } } } 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 } dep.w &= ~trackOpBit dep.n &= ~trackOpBit } deps.length = ptr } }
依赖收集 通过前面的baseHanlder 可以知道收集依赖是在访问当前属性时触发函数 track进行收集,代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 const targetMap = new WeakMap <any , KeyToDepMap >()export function track (target: object , type : TrackOpTypes, key: unknown ) { 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) } } export function trackEffects ( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked (dep)) { dep.n |= trackOpBit shouldTrack = !wasTracked (dep) } } else { shouldTrack = !dep.has (activeEffect!) } if (shouldTrack) { dep.add (activeEffect!) activeEffect!.deps .push (dep) if (__DEV__ && activeEffect!.onTrack ) { activeEffect!.onTrack ({ effect : activeEffect!, ...debuggerEventExtraInfo! }) } } }
依赖触发 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 export function track (target: object , type : TrackOpTypes, key: unknown ) { 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) } } export function trackEffects ( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked (dep)) { dep.n |= trackOpBit shouldTrack = !wasTracked (dep) } } else { shouldTrack = !dep.has (activeEffect!) } if (shouldTrack) { dep.add (activeEffect!) activeEffect!.deps .push (dep) if (__DEV__ && activeEffect!.onTrack ) { activeEffect!.onTrack ({ effect : activeEffect!, ...debuggerEventExtraInfo! }) } } } 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 ) { deps = [...depsMap.values ()] } else if (key === 'length' && isArray (target)) { const newLength = Number (newValue) depsMap.forEach ((dep, key ) => { if (key === 'length' || key >= newLength) { deps.push (dep) } }) } else { if (key !== void 0 ) { deps.push (depsMap.get (key)) } switch (type ) { case TriggerOpTypes .ADD : if (!isArray (target)) { deps.push (depsMap.get (ITERATE_KEY )) if (isMap (target)) { deps.push (depsMap.get (MAP_KEY_ITERATE_KEY )) } } else if (isIntegerKey (key)) { deps.push (depsMap.get ('length' )) } break case TriggerOpTypes .DELETE : if (!isArray (target)) { deps.push (depsMap.get (ITERATE_KEY )) if (isMap (target)) { deps.push (depsMap.get (MAP_KEY_ITERATE_KEY )) } } break case TriggerOpTypes .SET : if (isMap (target)) { deps.push (depsMap.get (ITERATE_KEY )) } break } } const eventInfo = __DEV__ ? {target, type , key, newValue, oldValue, oldTarget} : undefined if (deps.length === 1 ) { if (deps[0 ]) { if (__DEV__) { triggerEffects (deps[0 ], eventInfo) } else { triggerEffects (deps[0 ]) } } } else { const effects : ReactiveEffect [] = [] for (const dep of deps) { if (dep) { effects.push (...dep) } } if (__DEV__) { triggerEffects (createDep (effects), eventInfo) } else { triggerEffects (createDep (effects)) } } } export function triggerEffects ( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { const effects = isArray (dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed ) { triggerEffect (effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed ) { triggerEffect (effect, debuggerEventExtraInfo) } } } function triggerEffect ( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse ) { if (__DEV__ && effect.onTrigger ) { effect.onTrigger (extend ({effect}, debuggerEventExtraInfo)) } if (effect.scheduler ) { effect.scheduler () } else { effect.run () } } }