阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > effect函数的内部工作原理

effect函数的内部工作原理

作者:陈川 阅读数:3107人阅读 分类: Vue.js

effect函数的内部工作原理

effect函数是Vue3响应式系统的核心,它负责创建响应式副作用,当依赖的响应式数据变化时自动重新执行。理解effect的工作原理对深入掌握Vue3响应式机制至关重要。

基本使用示例

import { reactive, effect } from 'vue'

const state = reactive({ count: 0 })

effect(() => {
  console.log('count changed:', state.count)
})

state.count++ // 触发effect重新执行

核心数据结构

effect内部维护几个关键数据结构:

  1. activeEffect:当前正在运行的effect引用
  2. effectStack:effect调用栈
  3. targetMap:存储所有响应式对象及其属性的依赖关系
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

let activeEffect: ReactiveEffect | undefined
const effectStack: ReactiveEffect[] = []

effect创建过程

当调用effect(fn)时:

  1. 创建ReactiveEffect实例
  2. 立即执行effect.run()
  3. 返回runner函数用于手动控制
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
}

依赖收集机制

依赖收集发生在属性访问时:

  1. track函数建立响应式对象属性与effect的映射关系
  2. 使用WeakMap+Map+Set三级结构存储依赖
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)
  }
}

触发更新机制

当响应式数据变化时:

  1. trigger函数查找所有依赖该属性的effect
  2. 根据情况直接执行或通过调度器执行
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))
  }
  
  // 处理数组length等特殊情况
  // ...
  
  effects.forEach(effect => {
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  })
}

嵌套effect处理

effect支持嵌套调用,通过effectStack维护正确的父子关系:

effect(() => {
  console.log('outer effect:', state.count)
  
  effect(() => {
    console.log('inner effect:', state.double)
  })
})

调度器机制

effect支持自定义调度逻辑:

effect(() => {
  console.log(state.count)
}, {
  scheduler(effect) {
    // 自定义调度逻辑
    requestAnimationFrame(effect.run)
  }
})

停止effect

通过返回的runner可以停止effect:

const runner = effect(() => { /*...*/ })
runner.effect.stop() // 停止响应

性能优化策略

effect内部实现了多项优化:

  1. 避免重复收集:通过effectStack防止同一effect重复收集
  2. 惰性执行:调度器可以控制执行时机
  3. 清理机制:每次运行前清理旧依赖
function cleanup(effect) {
  const { deps } = effect
  for (let i = 0; i < deps.length; i++) {
    deps[i].delete(effect)
  }
  effect.deps.length = 0
}

与computed/watch的关系

Vue3的computed和watch都是基于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
    }
  }
}

响应式边界情况处理

effect需要处理各种边界情况:

  1. 自增操作避免无限循环
  2. 数组方法特殊处理
  3. 原型链属性访问
// 避免无限循环的例子
const state = reactive({ count: 0 })
effect(() => {
  // 如果没有保护机制,这会无限循环
  state.count = state.count + 1
})

调试支持

effect提供丰富的调试钩子:

effect(() => {
  // ...
}, {
  onTrack(e) {
    debugger
  },
  onTrigger(e) {
    debugger
  }
})

与渲染系统的协作

组件渲染本质上是一个effect:

instance.update = effect(function componentEffect() {
  if (!instance.isMounted) {
    // 首次挂载
    const subTree = render.call(proxy)
    patch(null, subTree, container)
    instance.isMounted = true
  } else {
    // 更新
    const nextTree = render.call(proxy)
    patch(prevTree, nextTree, container)
  }
}, { scheduler: queueJob })

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌