The internal working principle of the effect function
Internal Working Principle of the effect Function
The effect
function is the core of Vue 3's reactivity system. It is responsible for creating reactive side effects that automatically re-execute when the dependent reactive data changes. Understanding how effect
works is crucial for mastering Vue 3's reactivity mechanism.
Basic Usage Example
import { reactive, effect } from 'vue'
const state = reactive({ count: 0 })
effect(() => {
console.log('count changed:', state.count)
})
state.count++ // Triggers the effect to re-execute
Core Data Structures
The effect
function internally maintains several key data structures:
- activeEffect: A reference to the currently running effect.
- effectStack: The effect call stack.
- targetMap: Stores the dependency relationships of all reactive objects and their properties.
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
let activeEffect: ReactiveEffect | undefined
const effectStack: ReactiveEffect[] = []
Effect Creation Process
When effect(fn)
is called:
- A
ReactiveEffect
instance is created. - The
effect.run()
method is immediately executed. - A runner function is returned for manual control.
class ReactiveEffect {
constructor(
public fn: () => T,
public scheduler?: () => void
) {}
run() {
if (!effectStack.includes(this)) {
try {
effectStack.push((activeEffect = this))
return this.fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
Dependency Collection Mechanism
Dependency collection occurs during property access:
- The
track
function establishes a mapping relationship between reactive object properties and effects. - A three-level structure of
WeakMap + Map + Set
is used to store dependencies.
function track(target, type, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}
Update Trigger Mechanism
When reactive data changes:
- The
trigger
function looks up all effects dependent on the property. - Effects are executed either directly or through a scheduler.
function trigger(target, type, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = new Set<ReactiveEffect>()
const addEffects = (dep: Dep) => {
dep.forEach(effect => {
if (effect !== activeEffect) {
effects.add(effect)
}
})
}
if (key !== void 0) {
addEffects(depsMap.get(key))
}
// Handle special cases like array length
// ...
effects.forEach(effect => {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
})
}
Nested Effect Handling
Nested effects are supported, with the effectStack
maintaining correct parent-child relationships:
effect(() => {
console.log('outer effect:', state.count)
effect(() => {
console.log('inner effect:', state.double)
})
})
Scheduler Mechanism
Effects support custom scheduling logic:
effect(() => {
console.log(state.count)
}, {
scheduler(effect) {
// Custom scheduling logic
requestAnimationFrame(effect.run)
}
})
Stopping an Effect
The returned runner can be used to stop an effect:
const runner = effect(() => { /*...*/ })
runner.effect.stop() // Stops reactivity
Performance Optimization Strategies
The effect
function implements several optimizations:
- Avoiding duplicate collection: The
effectStack
prevents the same effect from being collected repeatedly. - Lazy execution: The scheduler controls execution timing.
- Cleanup mechanism: Old dependencies are cleaned up before each run.
function cleanup(effect) {
const { deps } = effect
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
effect.deps.length = 0
}
Relationship with computed and watch
Vue 3's computed
and watch
are built on top of effect
:
function computed(getter) {
let dirty = true
const runner = effect(getter, {
lazy: true,
scheduler() {
dirty = true
}
})
return {
get value() {
if (dirty) {
value = runner()
dirty = false
}
return value
}
}
}
Handling Edge Cases in Reactivity
The effect
function handles various edge cases:
- Avoiding infinite loops with self-incrementing operations.
- Special handling for array methods.
- Prototype chain property access.
// Example of avoiding infinite loops
const state = reactive({ count: 0 })
effect(() => {
// Without safeguards, this would cause an infinite loop
state.count = state.count + 1
})
Debugging Support
The effect
function provides rich debugging hooks:
effect(() => {
// ...
}, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
Collaboration with the Rendering System
Component rendering is essentially an effect:
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// Initial mount
const subTree = render.call(proxy)
patch(null, subTree, container)
instance.isMounted = true
} else {
// Update
const nextTree = render.call(proxy)
patch(prevTree, nextTree, container)
}
}, { scheduler: queueJob })
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:依赖收集与追踪的详细流程
下一篇:响应式代理的创建过程