阿里云主机折上折
  • 微信号
Current Site:Index > The internal working principle of the effect function

The internal working principle of the effect function

Author:Chuan Chen 阅读数:3108人阅读 分类: Vue.js

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:

  1. activeEffect: A reference to the currently running effect.
  2. effectStack: The effect call stack.
  3. 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:

  1. A ReactiveEffect instance is created.
  2. The effect.run() method is immediately executed.
  3. 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:

  1. The track function establishes a mapping relationship between reactive object properties and effects.
  2. 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:

  1. The trigger function looks up all effects dependent on the property.
  2. 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:

  1. Avoiding duplicate collection: The effectStack prevents the same effect from being collected repeatedly.
  2. Lazy execution: The scheduler controls execution timing.
  3. 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:

  1. Avoiding infinite loops with self-incrementing operations.
  2. Special handling for array methods.
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.