Vue3 Reactive System Source Code Level Execution Flow Analysis
Vue3 Reactive System Source Code Level Execution Flow Analysis
Problem Description: Deeply analyze the complete source code level execution flow of Vue3's reactive system, from initialization to update triggering, including specific implementation details of core stages like dependency collection and update triggering.
Solution Process:
1. Reactive Object Creation Stage
// reactive() function entry
function reactive(target) {
return createReactiveObject(target, false, mutableHandlers)
}
function createReactiveObject(target, isReadonly, baseHandlers) {
// 1. Check if already a reactive object
if (target.__v_raw) return target
// 2. Create reactive proxy using Proxy
const proxy = new Proxy(target, baseHandlers)
// 3. Establish mapping between raw object and proxy object
reactiveMap.set(target, proxy)
return proxy
}
2. Core Implementation of Dependency Collection
// get interceptor in mutableHandlers
const get = createGetter()
function createGetter() {
return function get(target, key, receiver) {
// 1. Get raw value
const res = Reflect.get(target, key, receiver)
// 2. Perform dependency collection (core)
track(target, "get", key)
// 3. If value is an object, recursively make it reactive
if (isObject(res)) {
return reactive(res)
}
return res
}
}
// track function implements dependency collection
function track(target, type, key) {
// 1. Get the currently executing side effect function
let activeEffect = effectStack[effectStack.length - 1]
if (!activeEffect) return
// 2. Get the dependency map for the target
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 3. Get the dependency set for the key
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
// 4. Establish bidirectional association
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep) // Side effect function records which dependency sets it belongs to
}
}
3. Side Effect Function (effect) Registration Mechanism
function effect(fn, options = {}) {
// 1. Create side effect function
const effect = createReactiveEffect(fn, options)
// 2. If not lazy execution, execute immediately for initial dependency collection
if (!options.lazy) {
effect()
}
return effect
}
function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
// Prevent recursive calls
if (!effectStack.includes(effect)) {
try {
// 1. Push to top of stack, set as currently active effect
effectStack.push(effect)
activeEffect = effect
// 2. Execute original function, trigger dependency collection
return fn()
} finally {
// 3. After execution, pop from stack
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
}
// Record related metadata
effect.deps = [] // All dependency sets this effect belongs to
effect.options = options
return effect
}
4. Complete Flow of Triggering Updates
// set interceptor implementation
const set = createSetter()
function createSetter() {
return function set(target, key, value, receiver) {
// 1. Get old value, determine operation type (add/update)
const oldValue = target[key]
const hadKey = hasOwn(target, key)
// 2. Perform reflection set
const result = Reflect.set(target, key, value, receiver)
// 3. Trigger update (core)
if (!hadKey) {
trigger(target, "add", key, value)
} else if (value !== oldValue) {
trigger(target, "set", key, value, oldValue)
}
return result
}
}
// trigger function implements update triggering
function trigger(target, type, key, newValue, oldValue) {
// 1. Get all dependencies for the target
const depsMap = targetMap.get(target)
if (!depsMap) return
// 2. Create set of effects to execute (avoid duplicates and recursion)
const effects = new Set()
const add = (effectsToAdd) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect) { // Avoid currently executing effect
effects.add(effect)
}
})
}
}
// 3. Collect relevant dependencies based on operation type
if (key !== void 0) {
add(depsMap.get(key)) // Dependencies for this key
}
// 4. Special handling for arrays
if (type === 'add' && isArray(target)) {
add(depsMap.get('length')) // Array addition affects length
}
// 5. Execute all collected effects
const run = (effect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect) // Use scheduler
} else {
effect() // Execute directly
}
}
effects.forEach(run)
}
5. Scheduler and Batch Updates
// Scheduler used for Vue component updates
const scheduler = (effect) => {
// 1. Add effect to queue for batch update preparation
if (!queue.includes(effect)) {
queue.push(effect)
// 2. Use microtask to delay execution, achieving batch updates
if (!isFlushing) {
nextTick(flushJobs)
}
}
}
function flushJobs() {
isFlushing = true
// Sort to ensure: parent components update before child components
queue.sort((a, b) => a.id - b.id)
// Batch execute all effects
for (let i = 0; i < queue.length; i++) {
const effect = queue[i]
effect()
}
// Clear queue
queue.length = 0
isFlushing = false
}
Key Execution Flow Summary:
- Initialization Stage: Proxy interceptor setup, creation of reactive objects.
- Dependency Collection Stage: During effect execution, establish target→key→effect mapping via getter.
- Update Triggering Stage: Setter finds corresponding effects via mapping and executes them in batches via scheduler.
- Cleanup Stage: Clean up old dependencies before effect re-execution to avoid invalid updates.
This flow ensures the efficiency and precision of Vue3's reactive system, forming the core foundation of Vue component updates.