Eine Nebenwirkungsfunktion bezieht sich auf eine Funktion, die Nebenwirkungen erzeugt, wie im folgenden Code gezeigt:
function effect(){ document.body.innerText = 'hello vue3' }
Wenn die Effektfunktion ausgeführt wird , wird der Textinhalt des Körpers festgelegt, aber jede andere Funktion als die Effektfunktion kann den Textinhalt des Körpers lesen oder festlegen. Mit anderen Worten: Die Ausführung der Effektfunktion wirkt sich direkt oder indirekt auf die Ausführung anderer Funktionen aus. Zu diesem Zeitpunkt sagen wir, dass die Effektfunktion Nebenwirkungen hat. Nebenwirkungen können leicht auftreten, wenn beispielsweise eine Funktion eine globale Variable ändert, ist dies tatsächlich ein Nebeneffekt.
// 全局变量 let val = 1 function effect() { val = 2 // 修改全局变量,产生副作用 }
Im Nebeneffektmodul sind mehrere globale Variablen definiert, die uns helfen, die Generierung und den Aufruf zu verstehen von Nebenwirkungsprozessen.
// packages/reactivity/src/effect.ts export type Dep = Set<ReactiveEffect> & TrackedMarkers type KeyToDepMap = Map<any, Dep> // WeakMap 集合存储副作用函数 const targetMap = new WeakMap<any, KeyToDepMap>() // 用一个全局变量存储当前激活的 effect 函数 export let activeEffect: ReactiveEffect | undefined // 标识是否开启了依赖收集 export let shouldTrack = true const trackStack: boolean[] = []
targetMap ist eine Sammlung vom Typ WeakMap, die zum Speichern von Nebeneffektfunktionen verwendet wird. Aus der Typdefinition können wir die Datenstruktur von targetMap erkennen: #🎜🎜 ##🎜 🎜#
target --> Map
target --> Map
构成
Map 由 key --> Set
key -- > Set
bildet const map = new Map(); const weakMap = new WeakMap(); (function() { const foo = {foo: 1}; const bar = {bar: 2}; map.set(foo, 1); // foo 对象是 map 的key weakMap.set(bar, 2); // bar 对象是 weakMap 的 key })
Wenn der Funktionsausdruck für das Objekt foo ausgeführt wird, wird er immer noch als Schlüssel der Karte referenziert, sodass Garbage Collector ihn nicht aus dem Speicher entfernt , können wir das Objekt foo weiterhin über map.keys ausdrucken.
Da der Schlüssel von WeakMap für die Objektleiste eine schwache Referenz ist, hat er keinen Einfluss auf die Arbeit des Garbage Collectors. Sobald der Ausdruck ausgeführt wird, verschiebt der Garbage Collector die Objektleiste aus dem Speicher Außer, und wir können den Schlüsselwert von schwachMap nicht erhalten, wir können die Objektleiste nicht über schwachMap erhalten.
Einfach ausgedrückt: WeakMap ist ein schwacher Verweis auf den Schlüssel und hat keinen Einfluss auf die Arbeit des Garbage Collectors **. Gemäß dieser Funktion sind der entsprechende Schlüssel und der entsprechende Wert nicht mehr zugänglich, sobald der Schlüssel vom Garbage Collector recycelt wird. Daher wird WeakMap häufig zum Speichern von Informationen verwendet, die nur dann wertvoll sind, wenn das durch den Schlüssel referenzierte Objekt vorhanden ist (nicht recycelt wurde)**.
Wenn das Zielobjekt im obigen Szenario beispielsweise keine Referenzen hat, bedeutet dies, dass die Benutzerseite es nicht mehr benötigt und der Garbage Collector die Recyclingaufgabe abschließt. Wenn Sie jedoch Map anstelle von WeakMap verwenden, wird das Ziel nicht recycelt, selbst wenn der benutzerseitige Code keinen Verweis auf das Ziel enthält, was schließlich zu einem Speicherüberlauf führen kann.
activeEffect-Variable wird verwendet, um die aktuell ausgeführten Nebenwirkungen aufrechtzuerhalten.
shouldTrack-Variable wird verwendet, um Identifizieren Sie, ob die Abhängigkeitssammlung aktiviert ist. Die Abhängigkeitssammlung wird nur durchgeführt, wenn der Wert von ShouldTrack wahr ist, dh die Nebeneffektfunktion wird zur Abhängigkeitssammlung hinzugefügt.
Effekt-API wird verwendet, um eine Nebeneffektfunktion zu erstellen, die zwei Parameter akzeptiert, die benutzerdefiniert sind fn Funktionen und Optionen Optionen. Der Quellcode lautet wie folgt:
// packages/reactivity/src/effect.ts 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) if (options.scope) recordEffectScope(_effect, options.scope) } // 如果不是延迟执行的,则立即执行一次副作用函数 if (!options || !options.lazy) { _effect.run() } // 通过 bind 函数返回一个新的副作用函数 const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 将副作用添加到新的副作用函数上 runner.effect = _effect // 返回这个新的副作用函数 return runner }
Aus dem obigen Code können wir erkennen, dass die ursprüngliche Funktion dieser Nebenwirkung fn zugewiesen wird, wenn im übergebenen Parameter fn eine Nebenwirkung vorliegt. Rufen Sie dann die ReactiveEffect-Klasse auf, um eine gekapselte Nebeneffektfunktion zu erstellen.
In einigen Szenarien möchten wir nicht, dass der Effekt sofort ausgeführt wird, sondern dass er bei Bedarf ausgeführt wird. Dies können wir erreichen, indem wir den Optionen das Lazy-Attribut hinzufügen. Bestimmen Sie im Quellcode der Effektfunktion den Wert der Option options.lazy. Wenn der Wert wahr ist, wird die Nebeneffektfunktion nicht sofort ausgeführt, wodurch ein verzögerter Ausführungseffekt erzielt wird.
Geben Sie dann über die Bindungsfunktion einen neuen Nebeneffektfunktionsläufer zurück. Diese neue Funktion wird als _effect bezeichnet, und _effect wird zum Effektattribut der neuen Nebeneffektfunktion hinzugefügt und gibt schließlich diese neue Seite zurück Effektfunktionen.
Da die Effekt-API die gekapselte Nebeneffektfunktion zurückgibt, wird die ursprüngliche Nebeneffektfunktion im Effektattribut der gekapselten Nebeneffektfunktion gespeichert, wenn Sie also die Nebeneffektfunktion übergeben möchten Als Benutzer müssen Sie fn.effect.fn übergeben, um es zu erhalten.
Die ReactiveEffect-Klasse wird in der Effektfunktion aufgerufen, um Nebenwirkungen zu erzeugen. Schauen wir uns als Nächstes die Implementierung der ReactiveEffect-Klasse an.
// packages/reactivity/src/effect.ts export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl<T> /** * @internal */ allowRecurse?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { recordEffectScope(this, scope) } run() { // 如果 effect 已停用,返回原始副作用函数执行后的结果 if (!this.active) { return this.fn() } let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack while (parent) { if (parent === this) { return } parent = parent.parent } try { // 创建一个新的副作用前将当前正在执行的副作用存储到新建的副作用的 parent 属性上,解决嵌套effect 的情况 this.parent = activeEffect // 将创建的副作用设置为当前正则正在执行的副作用 activeEffect = this // 将 shouldTrack 设置为 true,表示开启依赖收集 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 } } // 停止(清除) effect stop() { if (this.active) { cleanupEffect(this) if (this.onStop) { this.onStop() } this.active = false } } }
In der ReactiveEffect-Klasse ist eine Ausführungsmethode definiert, die die eigentliche Ausführungsmethode beim Erstellen von Nebenwirkungen ist. Jedes Mal, wenn ein Update versendet wird, wird diese Ausführungsmethode ausgeführt und dadurch der Wert aktualisiert.
全局变量 activeEffect 用来维护当前正在执行的副作用,当存在嵌套渲染组件的时候,依赖收集后,副作用函数会被覆盖,即 activeEffect 存储的副作用函数在嵌套 effect 的时候会被内层的副作用函数覆盖。为了解决这个问题,在 run 方法中,将当前正在执行的副作用activeEffect保存到新建的副作用的 parent 属性上,然后再将新建的副作用设置为当前正在执行的副作用。在新建的副作用执行完毕后,再将存储到 parent 属性的副作用重新设置为当前正在执行的副作用。
在 ReactiveEffect 类中,还定义了一个 stop 方法,该方法用来停止并清除当前正在执行的副作用。
当使用代理对象访问对象的属性时,就会触发代理对象的 get 拦截函数执行,如下面的代码所示:
const obj = { foo: 1 } const p = new Proxy(obj, { get(target, key, receiver) { track(target, key) return Reflect.get(target, key, receiver) } }) p.foo
在上面的代码中,通过代理对象p 访问 foo 属性,便会触发 get 拦截函数的执行,此时就在 get 拦截函数中调用 track 函数进行依赖收集。源码中 get 拦截函数的解析可阅读《Vue3 源码解读之非原始值的响应式原理》一文中的「访问属性的拦截」小节。
下面,我们来看看 track 函数的实现。
// packages/reactivity/src/effect.ts // 收集依赖 export function track(target: object, type: TrackOpTypes, key: unknown) { // 如果开启了依赖收集并且有正在执行的副作用,则收集依赖 if (shouldTrack && activeEffect) { // 在 targetMap 中获取对应的 target 的依赖集合 let depsMap = targetMap.get(target) if (!depsMap) { // 如果 target 不在 targetMap 中,则加入,并初始化 value 为 new Map() targetMap.set(target, (depsMap = new Map())) } // 从依赖集合中获取对应的 key 的依赖 let dep = depsMap.get(key) if (!dep) { // 如果 key 不存在,将这个 key 作为依赖收集起来,并初始化 value 为 new Set() depsMap.set(key, (dep = createDep())) } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }
在 track 函数中,通过一个 if 语句判断是否进行依赖收集,只有当 shouldTrack 为 true 并且存在 activeEffect,即开启了依赖收集并且存在正在执行的副作用时,才进行依赖收集。
然后通过 target 对象从 targetMap 中尝试获取对应 target 的依赖集合depsMap,如果 targetMap 中不存在当前target的依赖集合,则将当前 target 添加进 targetMap 中,并将 targetMap 的 value 初始化为 new Map()。
// 在 targetMap 中获取对应的 target 的依赖集合 let depsMap = targetMap.get(target) if (!depsMap) { // 如果 target 不在 targetMap 中,则加入,并初始化 value 为 new Map() targetMap.set(target, (depsMap = new Map())) }
接着根据target中被读取的 key,从依赖集合depsMap中获取对应 key 的依赖,如果依赖不存在,则将这个 key 的依赖收集到依赖集合depsMap中,并将依赖初始化为 new Set()。
// 从依赖集合中获取对应的 key 的依赖 let dep = depsMap.get(key) if (!dep) { // 如果 key 不存在,将这个 key 作为依赖收集起来,并初始化 value 为 new Set() depsMap.set(key, (dep = createDep())) }
最后调用 trackEffects 函数,将副作用函数收集到依赖集合depsMap中。
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 // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. // 如果依赖中并不存当前的 effect 副作用函数 shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 将当前的副作用函数收集进依赖中 dep.add(activeEffect!) // 并在当前副作用函数的 deps 属性中记录该依赖 activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack( Object.assign( { effect: activeEffect! }, debuggerEventExtraInfo ) ) } } }
在 trackEffects 函数中,检查当前正在执行的副作用函数 activeEffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
当对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:
const obj = { foo: 1 } const p = new Proxy(obj, { // 拦截设置操作 set(target, key, newVal, receiver){ // 如果属性不存在,则说明是在添加新属性,否则设置已有属性 const type = Object.prototype.hasOwnProperty.call(target,key) ? 'SET' : 'ADD' // 设置属性值 const res = Reflect.set(target, key, newVal, receiver) // 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数 trigger(target,key,type) return res } // 省略其他拦截函数 }) p.foo = 2
在上面的代码中,通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行,此时就在 set 拦截函数中调用 trigger 函数中派发更新。源码中 set 拦截函数的解析可阅读《Vue3 源码解读之非原始值的响应式原理》一文中的「设置属性操作的拦截」小节。
下面,我们来看看 track 函数的实现。
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) // 该 target 从未被追踪,则不继续执行 if (!depsMap) { // never been tracked return } // 存放所有需要派发更新的副作用函数 let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target // 当需要清除依赖时,将当前 target 的依赖全部传入 deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { // 处理数组的特殊情况 depsMap.forEach((dep, key) => { // 如果对应的长度, 有依赖收集需要更新 if (key === 'length' || key >= (newValue as number)) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE // 在 SET | ADD | DELETE 的情况,添加当前 key 的依赖 if (key !== void 0) { deps.push(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 ADD 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 DELETE 时触发Map 数据结构的 keys 方法的副作用函数重新执行 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[] = [] // 将需要执行的副作用函数收集到 effects 数组中 for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
在 trigger 函数中,首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。
const depsMap = targetMap.get(target) // 该 target 从未被追踪,则不继续执行 if (!depsMap) { // never been tracked return }
接着创建一个 Set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。
// 存放所有需要派发更新的副作用函数 let deps: (Dep | undefined)[] = []
接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。
如果操作类型是 TriggerOpTypes.CLEAR ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。
if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target // 当需要清除依赖时,将当前 target 的依赖全部传入 deps = [...depsMap.values()] }
如果操作目标是数组,并且修改了数组的 length 属性,需要把与 length 属性相关联的副作用函数以及索引值大于或等于新的 length 值元素的相关联的副作用函数从 depsMap 中取出并添加到 deps 集合中。
else if (key === 'length' && isArray(target)) { // 如果操作目标是数组,并且修改了数组的 length 属性 depsMap.forEach((dep, key) => { // 对于索引大于或等于新的 length 值的元素, // 需要把所有相关联的副作用函数取出并添加到 deps 中执行 if (key === 'length' || key >= (newValue as number)) { deps.push(dep) } }) }
如果当前的 key 不为 undefined,则将与当前key相关联的副作用函数添加到 deps 集合中。注意这里的判断条件 void 0,是通过 void 运算符的形式表示 undefined 。
if (key !== void 0) { deps.push(depsMap.get(key)) }
接下来通过 Switch 语句来收集操作类型为 ADD、DELETE、SET 时与 ITERATE_KEY 和 MAP_KEY_ITERATE_KEY 相关联的副作用函数。
// also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 ADD 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { // 操作类型为 DELETE 时触发Map 数据结构的 keys 方法的副作用函数重新执行 deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break }
最后调用 triggerEffects 函数,传入收集的副作用函数,执行派发更新。
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[] = [] // 将需要执行的副作用函数收集到 effects 数组中 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 ) { // spread into array for stabilization // 遍历需要执行的副作用函数集合 for (const effect of isArray(dep) ? dep : [...dep]) { // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行 if (effect !== activeEffect || effect.allowRecurse) { if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } if (effect.scheduler) { // 如果一个副作用函数存在调度器,则调用该调度器 effect.scheduler() } else { // 否则直接执行副作用函数 effect.run() } } } }
在 triggerEffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
Das obige ist der detaillierte Inhalt vonBeispielanalyse für Vue3-Nebeneffektfunktionen und Abhängigkeitssammlung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!