vue3源码

一、vue3 computed和watch源码

一、computed 的实现原理

1. 核心思想

  • 惰性求值:只有在真正被访问时才会计算值,且依赖未变化时会复用缓存值。

  • 依赖追踪:内部通过 effect 跟踪依赖的响应式变量,当依赖变化时标记缓存失效。

  • 触发更新:当缓存失效时,触发依赖它的副作用(如组件渲染)重新执行。

2. 源码结构

computed 的实现在 packages/reactivity/src/computed.ts 中,核心类是 ComputedRefImpl

class ComputedRefImpl<T> {
  private _value!: T
  private _dirty = true // 标记是否需要重新计算
  public readonly effect: ReactiveEffect<T>
  public readonly __v_isRef = true

  constructor(
    getter: () => T,
    private readonly _setter: (v: any) => void
  ) {
    // 创建一个 effect,用于依赖收集
    this.effect = new ReactiveEffect(getter, () => {
      if (!this._dirty) {
        this._dirty = true
        trigger(toRaw(this), TriggerOpTypes.SET, 'value') // 触发依赖更新
      }
    })
  }

  get value() {
    // 访问时收集依赖
    track(toRaw(this), TrackOpTypes.GET, 'value')
    if (this._dirty) {
      this._value = this.effect.run() // 重新计算值
      this._dirty = false
    }
    return this._value
  }

  set value(newValue: any) {
    this._setter(newValue) // 调用用户定义的 setter
  }
}

3. 关键流程

  1. 初始化:创建 ReactiveEffect,传入 getter 和一个调度器函数(标记 _dirty 并触发更新)。

  2. 取值(get value)

    • 通过 track 收集当前正在运行的 effect(如组件渲染的 effect)。

    • 如果 _dirty 为 true,运行 getter 计算新值并缓存。

  3. 依赖变化

    • 当 getter 中的依赖变化时,触发调度器函数,标记 _dirty = true

    • 通过 trigger 通知所有依赖此 computed 的副作用重新执行。

  4. 赋值(set value):调用用户定义的 setter(通常用于可写计算属性)。

 

二、watch 的实现原理

1. 核心思想

  • 监听变化:基于 effect 监听一个或多个响应式数据源。

  • 异步回调:默认将回调推入微任务队列,避免同步重复调用。

  • 新旧值对比:通过 effect.run() 获取新值,并保存旧值供回调使用。

2. 源码结构

watch 的实现在 packages/runtime-core/src/apiWatch.ts,核心函数是 doWatch

function doWatch(
  source: WatchSource,
  cb: WatchCallback | null,
  { immediate, deep, flush }: WatchOptions = EMPTY_OBJ
): WatchStopHandle {
  // 1. 标准化 source 为 getter 函数
  const getter = () => source()

  // 2. 创建调度器(控制回调执行时机)
  let scheduler: (job: () => void) => void
  if (flush === 'sync') {
    scheduler = (job) => job() // 同步执行
  } else if (flush === 'pre') {
    scheduler = (job) => queueJob(job) // 组件更新前执行
  } else {
    scheduler = (job) => queuePostFlushCb(job) // 微任务队列执行(默认)
  }

  // 3. 创建 ReactiveEffect
  const effect = new ReactiveEffect(getter, scheduler)
  
  // 4. 保存旧值并定义回调
  let oldValue: any
  const job = () => {
    if (cb) {
      const newValue = effect.run()
      if (deep || hasChanged(newValue, oldValue)) {
        cb(newValue, oldValue)
        oldValue = newValue
      }
    }
  }

  // 5. 初始化和依赖收集
  oldValue = effect.run()

  // 返回停止监听的函数
  return () => effect.stop()
}

3. 关键流程

  1. 标准化 source:将监听源转换为 getter 函数(如 () => obj.prop)。

  2. 创建调度器

    • 默认使用微任务队列(queuePostFlushCb),确保回调在 DOM 更新后执行。

    • 支持 sync(同步)、pre(组件更新前)等模式。

  3. 创建 ReactiveEffect

    • getter 作为副作用函数,内部访问的响应式数据会被自动收集为依赖。

    • 依赖变化时触发调度器,而非直接运行 getter

  4. 回调执行

    • 当依赖变化时,调度器将 job 推入队列。

    • job 中对比新旧值,若变化则执行用户回调。  

 

二、computed里面的trigger,track都是什么,讲下源码实现

 

一、track 和 trigger 的作用

  1. track

    • 依赖收集:当一个响应式对象的属性被访问(例如在 computed 的 getter 中访问另一个 ref),Vue 会记录当前正在运行的 effect(副作用函数),并建立依赖关系。

    • 数据存储:所有依赖关系会被存储在一个全局的 WeakMaptargetMap)中,结构为:
      target -> key -> Set<effects>

  2. trigger

    • 触发更新:当响应式对象的属性被修改时,Vue 会根据 targetMap 找到所有依赖该属性的 effect,并重新执行它们(例如 computed 的重新计算或组件的重新渲染)。


二、源码实现分析

2. track 的实现

当访问响应式对象的属性时,调用 track 收集当前 effect

// packages/reactivity/src/effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) 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) // 将当前 effect 添加到依赖集合
    activeEffect.deps.push(dep) // 反向关联,用于清理依赖
  }
}

  

关键逻辑

  • activeEffect 是当前正在运行的 effect(例如 computed 的 getter 函数对应的 effect)。

  • 将 activeEffect 添加到 target.key 对应的依赖集合 dep 中。

  • 反向关联effect.deps 会记录所有依赖它的 dep,用于后续清理(如 effect 停止时)。


3. trigger 的实现

当修改响应式对象的属性时,调用 trigger 触发依赖更新:

// packages/reactivity/src/effect.ts
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  const effects = new Set<ReactiveEffect>()
  // 收集所有需要触发的 effect
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.allowRecurse) {
          effects.add(effect)
        }
      })
    }
  }

  // 根据操作类型处理不同情况
  if (type === TriggerOpTypes.CLEAR) {
    // 清空整个对象的依赖
    depsMap.forEach(add)
  } else if (key !== undefined) {
    // 触发特定 key 的依赖
    add(depsMap.get(key))
  }

  // 触发 effect 的调度执行
  effects.forEach(effect => {
    if (effect.scheduler) {
      effect.scheduler() // 优先执行自定义调度器(如 computed 的异步更新)
    } else {
      effect.run() // 直接运行 effect
    }
  })
}

 

posted @ 2025-03-15 10:49  恣肆zisi  阅读(369)  评论(0)    收藏  举报