Vue3 的响应式系统源码级 watch 与 watchEffect 的实现原理与区别
字数 1631 2025-11-19 04:25:29

Vue3 的响应式系统源码级 watch 与 watchEffect 的实现原理与区别

1. 核心概念与使用场景

watchwatchEffect 都是 Vue3 响应式系统中用于监听数据变化的 API,但它们的执行时机和依赖收集方式不同:

  • watch:需显式指定监听的数据源和回调函数,默认惰性执行(首次不执行),可获取变化前后的值。
  • watchEffect:自动收集回调函数内的响应式依赖,立即执行,无需指定数据源,但无法直接获取旧值。

2. 实现原理分析

(1)底层依赖:effect 与调度器

Vue3 的响应式系统基于 effect(副作用函数)实现。watchwatchEffect 本质都是创建一个自定义的 effect,通过调度器(scheduler)控制回调触发时机。

(2)watchEffect 的源码级流程

  1. 立即执行与依赖收集

    function watchEffect(fn, options) {  
      // 创建 effect,并标记为 WatcherEffect  
      const effect = new ReactiveEffect(fn, () => {  
        // 调度器:当依赖变化时,触发回调  
        if (effect.active) {  
          fn(); // 默认立即执行  
        }  
      });  
      effect.run(); // 立即执行,收集依赖  
    }  
    
    • 执行 fn 时,会访问响应式数据,触发 get 操作,将当前 effect 关联到数据的依赖集合中。
    • 依赖变化时,通过调度器重新执行 fn
  2. 清理副作用

    • watchEffect 可接收一个 onCleanup 回调,用于清理上一次执行的副作用(如取消请求)。
    • 实现原理:在每次重新执行 fn 前,先执行上一次的 onCleanup

(3)watch 的源码级流程

  1. 数据源解析

    • 支持单个响应式对象(如 ref/reactive)或函数(如 () => obj.foo)。
    • 源码会调用 getter 函数获取值,并记录旧值。
  2. 惰性执行与调度控制

    function watch(source, cb, options) {  
      let getter = isRef(source) ? () => source.value : isFunction(source) ? source : () => traverse(source);  
      let oldValue;  
      const job = () => {  
        const newValue = effect.run(); // 获取新值  
        cb(newValue, oldValue); // 触发回调  
        oldValue = newValue;  
      };  
      const effect = new ReactiveEffect(getter, job); // 调度器为 job  
      if (options.immediate) {  
        job(); // 立即执行  
      } else {  
        oldValue = effect.run(); // 仅收集依赖,不触发回调  
      }  
    }  
    
    • 默认情况下,首次执行 effect.run() 只收集依赖,不触发回调(惰性)。
    • 依赖变化时,通过调度器 job 执行回调,并传入新值和旧值。
  3. 深度监听

    • 若数据源为对象,默认浅层监听。设置 deep: true 时,会递归遍历对象的所有属性,强制收集深层依赖。

3. 关键区别与设计思想

特性 watchEffect watch
依赖收集 自动收集回调中的所有响应式依赖 仅监听显式指定的数据源
执行时机 立即执行 默认惰性,可配置 immediate: true
旧值获取 不支持 支持
适用场景 依赖逻辑简单、无需旧值的场景 需要精确控制监听源或对比旧值的场景

4. 性能优化与陷阱

  1. watchEffect 的依赖陷阱
    • 若回调中包含条件分支,可能意外依赖不需要的数据。需手动清理无效依赖(如使用 onCleanup)。
  2. watch 的深度监听开销
    • deep: true 会递归遍历整个对象,大型对象可能性能较差。可改用特定路径的 getter 函数(如 () => obj.a.b)。

5. 总结

Vue3 通过复用 ReactiveEffect 和调度器机制,统一实现了 watchwatchEffect。两者的核心差异在于依赖收集的时机和粒度

  • watchEffect 更“智能”,适合逻辑简单的副作用。
  • watch 更“精确”,适合需要细粒度控制的场景。
Vue3 的响应式系统源码级 watch 与 watchEffect 的实现原理与区别 1. 核心概念与使用场景 watch 和 watchEffect 都是 Vue3 响应式系统中用于监听数据变化的 API,但它们的执行时机和依赖收集方式不同: watch :需显式指定监听的数据源和回调函数,默认 惰性执行 (首次不执行),可获取变化前后的值。 watchEffect :自动收集回调函数内的响应式依赖,立即执行,无需指定数据源,但无法直接获取旧值。 2. 实现原理分析 (1)底层依赖: effect 与调度器 Vue3 的响应式系统基于 effect (副作用函数)实现。 watch 和 watchEffect 本质都是创建一个自定义的 effect ,通过调度器( scheduler )控制回调触发时机。 (2) watchEffect 的源码级流程 立即执行与依赖收集 : 执行 fn 时,会访问响应式数据,触发 get 操作,将当前 effect 关联到数据的依赖集合中。 依赖变化时,通过调度器重新执行 fn 。 清理副作用 : watchEffect 可接收一个 onCleanup 回调,用于清理上一次执行的副作用(如取消请求)。 实现原理:在每次重新执行 fn 前,先执行上一次的 onCleanup 。 (3) watch 的源码级流程 数据源解析 : 支持单个响应式对象(如 ref / reactive )或函数(如 () => obj.foo )。 源码会调用 getter 函数获取值,并记录旧值。 惰性执行与调度控制 : 默认情况下,首次执行 effect.run() 只收集依赖,不触发回调(惰性)。 依赖变化时,通过调度器 job 执行回调,并传入新值和旧值。 深度监听 : 若数据源为对象,默认浅层监听。设置 deep: true 时,会递归遍历对象的所有属性,强制收集深层依赖。 3. 关键区别与设计思想 | 特性 | watchEffect | watch | |--------------|----------------------------------|------------------------------------| | 依赖收集 | 自动收集回调中的所有响应式依赖 | 仅监听显式指定的数据源 | | 执行时机 | 立即执行 | 默认惰性,可配置 immediate: true | | 旧值获取 | 不支持 | 支持 | | 适用场景 | 依赖逻辑简单、无需旧值的场景 | 需要精确控制监听源或对比旧值的场景 | 4. 性能优化与陷阱 watchEffect 的依赖陷阱 : 若回调中包含条件分支,可能意外依赖不需要的数据。需手动清理无效依赖(如使用 onCleanup )。 watch 的深度监听开销 : deep: true 会递归遍历整个对象,大型对象可能性能较差。可改用特定路径的 getter 函数(如 () => obj.a.b )。 5. 总结 Vue3 通过复用 ReactiveEffect 和调度器机制,统一实现了 watch 和 watchEffect 。两者的核心差异在于 依赖收集的时机和粒度 : watchEffect 更“智能”,适合逻辑简单的副作用。 watch 更“精确”,适合需要细粒度控制的场景。