【Vue3源码解析】响应式原理

源码环境搭建

【Vue3源码解析】应用实例创建及页面渲染-优快云博客

写文章时的Vue 版本:

"version": "3.5.13",

针对单个包进行开发环境打包、测试。

pnpm run dev reactivity

image-20250215180341644

image-20250215180401097

reactive 创建响应式对象

packages/reactivity/src/reactive.ts

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  /**
   * 通过调用 createReactiveObject 函数,创建一个响应式对象,并返回该对象。
   * 第一个参数是 目标对象
   * 第二个参数用来判断该对象是否是只读的
   * mutableHandlers 是一个对象,包含了响应式对象的一些操作方法,如get、set等。
   * mutableCollectionHandlers 是一个对象,包含了响应式集合对象的一些操作方法,如get、set等。
   * reactiveMap 是一个全局 WeakMap 对象,用于缓存 target 到 响应式对象的映射关系。
   */
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  if (!isObject(target)) {
    if (__DEV__) {
      warn(
        `value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
          target,
        )}`,
      )
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 如果已经是 Proxy 代理对象,则直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 如果 ProxyMap 中有 target 对应的 proxy 对象 则直接返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  // 类型无效 则直接返回 target
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 创建 target 对应的 Proxy 代理对象 并传入 baseHandlers
  const proxy = new Proxy(
    target,
    // COLLECTION => Map/Set/WeakMap/WeakSet
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

effect 和它的函数参数fn的作用

packages/reactivity/src/effect.ts

/**
 * 创建一个响应式副作用函数
 *
 * 此函数用于追踪函数内的响应式数据访问和修改,以便在数据变化时自动重新执行该函数
 * 它是响应式系统的核心部分,使得开发者可以轻松创建响应式的应用程序
 *
 * @param fn 要追踪的副作用函数,它会接收一个用于触发副作用的函数作为参数
 * @param options 可选的副作用选项,允许开发者自定义副作用的行为,例如是否应该停止追踪等
 * @returns 返回一个绑定了副作用函数的runner函数,用于手动触发副作用
 */
export function effect<T = any>(
  fn: () => T,
  // 可以由用户自定义,用于定义副作用effect的行为,例如shouldTrack、onTrack、onTrigger等
  // 这样可以覆盖ReactiveEffect的默认scheduler而执行自己的scheduler
  options?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {
  // 检查传入的函数是否已经是一个ReactiveEffect的副作用函数,如果是,则使用其内部的副作用函数
  if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }
  // 调用一次effect函数,根据传入的fn创建一个新的ReactiveEffect实例对象:_effect
  // 一个fn 对应一个 _effect对象
  // fn 同时成为_effect 对象的fn属性(形成闭包)
  /**
   * effect(fn)
   * fn.effect => ReactiveEffect对象
   * ReactiveEffect对象.fn => fn
   */

  // 创建一个新的ReactiveEffect实例
  /**
   * 当内部执行scheduler的时候,它会回头调用effect的run,·而run方法内部会调用fn
   * 意味着:scheduler()=>run()=>fn()
   * 如何执行:那么之后我们如果想要重新执行fn函数,·只需要执行scheduler就可以了
   */
  const e = new ReactiveEffect(fn)

  // 如果提供了用户自定义的配置,则将配置合并到副作用实例中
  if (options) {
    extend(e, options)
  }

  // 尝试运行副作用函数,如果在执行过程中抛出错误,则停止追踪并重新抛出错误
  try {
    e.run()
  } catch (err) {
    e.stop()
    throw err
  }

  // 创建并返回一个副作用函数的runner,它绑定了当前的副作用实例
  const runner = e.run.bind(e) as ReactiveEffectRunner
  runner.effect = e

  // 返回副作用函数的runner,允许手动触发副作用 e.run()=> fn()
  return runner
}
/**
 * 执行当前的副作用函数
 *
 * 此函数负责执行副作用操作,并在执行前后处理一些清理和准备依赖项的工作
 * 它还管理副作用的执行状态,确保在执行过程中不会被中断,并在完成后清理依赖项
 *
 * @returns 返回副作用函数的执行结果
 */
run(): T {
  // TODO cleanupEffect

  // 检查副作用是否处于非激活状态,如果是,则直接执行副作用函数
  if (!(this.flags & EffectFlags.ACTIVE)) {
    // stopped during cleanup
    return this.fn()
  }

  // 设置当前副作用为正在运行状态
  this.flags |= EffectFlags.RUNNING
  // 执行清理上一次执行的副作用
  cleanupEffect(this)
  // 准备依赖项收集
  prepareDeps(this)
  // 保存当前正在执行的副作用和是否应该跟踪的全局状态
  const prevEffect = activeSub
  const prevShouldTrack = shouldTrack
  // 设置当前执行的副作用和全局跟踪状态
  activeSub = this
  shouldTrack = true

  try {
    // 执行副作用函数
    return this.fn()
  } finally {
    // 在开发环境下,确保当前执行的副作用被正确恢复
    if (__DEV__ && activeSub !== this) {
      warn(
        'Active effect was not restored correctly - ' +
          'this is likely a Vue internal bug.',
      )
    }
    // 清理当前执行的副作用的依赖项
    cleanupDeps(this)
    // 恢复之前保存的正在执行的副作用和是否应该跟踪的全局状态
    activeSub = prevEffect
    shouldTrack = prevShouldTrack
    // 清除当前副作用的正在运行状态
    this.flags &= ~EffectFlags.RUNNING
  }
}

执行fn后响应式变量依赖的收集和触发

Vue3.4 版本之前最后的一层,也就是 nameDepMap 这一层是 Set 结构,但是从 3.4 版本开始,变成了 Map ,用来记录 track 的 id,然而Vue3.5对该部分又进行了重构,不再记录id,只能说学的速度永远跟不上技术更新的速度~

image-20250215181003656

packages/reactivity/src/baseHandlers.ts

/**
 * BaseReactiveHandler 类实现了 ProxyHandler 接口,用于处理响应式对象的代理操作。
 * 它主要负责拦截和处理对响应式对象的访问和操作,根据是否只读和是否浅层次来决定如何处理这些操作。
 *
 * @param _isReadonly 是否只读,如果为 true,则该响应式对象不可被修改。
 * @param _isShallow 是否浅层次,如果为 true,则仅对外层属性进行响应式处理,不递归处理内部属性。
 */
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _isShallow = false,
  ) {}

  /**
   * 拦截对目标对象属性的获取操作。
   * 此方法负责处理各种情况下的属性获取,包括处理特殊的 ReactiveFlags、数组的特殊情况、
   * 属性的追踪、以及返回值的处理等。
   *
   * @param target 目标对象,即被代理的响应式对象。
   * @param key 要访问的属性名。
   * @param receiver 代理对象或继承链上的其他对象。
   * @returns 属性值或处理后的值。
   */
  get(target: Target, key: string | symbol, receiver: object): any {
    // 处理特殊的 ReactiveFlags 属性
    if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]

    const isReadonly = this._isReadonly,
      isShallow = this._isShallow

    // 根据不同的 ReactiveFlags 返回相应的值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return isShallow
    } else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? isShallow
              ? shallowReadonlyMap
              : readonlyMap
            : isShallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver is not the reactive proxy, but has the same prototype
        // this means the receiver is a user proxy of the reactive proxy
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // early return undefined
      return
    }

    const targetIsArray = isArray(target)

    // 处理非只读情况下的特殊属性和方法
    if (!isReadonly) {
      let fn: Function | undefined
      if (targetIsArray && (fn = arrayInstrumentations[key])) {
        return fn
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    // 获取属性值,考虑内置符号和非跟踪键的情况
    const res = Reflect.get(
      target,
      key,
      // if this is a proxy wrapping a ref, return methods using the raw ref
      // as receiver so that we don't have to call `toRaw` on the ref in all
      // its class methods
      isRef(target) ? target : receiver,
    )

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 在非只读情况下,跟踪属性的访问
    if (!isReadonly) {
      // 依赖追踪
      track(target, TrackOpTypes.GET, key)
    }

    // 根据是否浅层次和返回值的类型,决定是否需要进一步处理返回值
    if (isShallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow = false) {
    super(false, isShallow)
  }

  /**
   * 设置一个响应式对象的属性。
   *
   * 该方法重写了 `Proxy` 对象的默认 `set` 行为,实现了自定义的属性设置逻辑。
   * 它处理了各种情况,例如浅层响应式、只读属性和 ref 解包,以确保响应式系统的正确行为。
   *
   * @param target - 被代理的原始对象。
   * @param key - 要设置的属性的名称或符号。
   * @param value - 属性的新值。
   * @param receiver - 代理对象本身。
   * @returns 返回设置属性的结果。
   */
  set(
    target: Record<string | symbol, unknown>,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
  let oldValue = target[key]
  // 如果不是浅层响应式模式,需要进一步处理属性和值。
  if (!this._isShallow) {
    const isOldValueReadonly = isReadonly(oldValue)
    // 如果新旧值都不是浅层或只读的,将它们转换为原始状态。
    if (!isShallow(value) && !isReadonly(value)) {
      oldValue = toRaw(oldValue)
      value = toRaw(value)
    }
    // 如果目标不是数组且旧值是 ref 而新值不是 ref,需要特殊处理。
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
      // 如果旧值是只读的,属性不能被设置。
      if (isOldValueReadonly) {
        return false
      } else {
        // 否则,直接设置旧值 ref 的值。
        oldValue.value = value
        return true
      }
    }
  } else {
    // 在浅层模式下,对象按原样设置,无论是否是响应式的。
  }

  // 确定键是否已经存在。
  const hadKey =
    isArray(target) && isIntegerKey(key)
      ? Number(key) < target.length
      : hasOwn(target, key)
  // 使用 Reflect.set 尝试设置属性。
  const result = Reflect.set(
    target,
    key,
    value,
    isRef(target) ? target : receiver,
  )
  // 如果目标是原始对象本身,根据属性是新增还是更新触发相应的副作用。
  if (target === toRaw(receiver)) {
    if (!hadKey) {
      // 触发新增属性的副作用。
      trigger(target, TriggerOpTypes.ADD, key, value)
    } else if (hasChanged(value, oldValue)) {
      // 触发更新属性的副作用。
      trigger(target, TriggerOpTypes.SET, key, value, oldValue)
    }
  }
  return result
}


  deleteProperty(
    target: Record<string | symbol, unknown>,
    key: string | symbol,
  ): boolean {
    const hadKey = hasOwn(target, key)
    const oldValue = target[key]
    const result = Reflect.deleteProperty(target, key)
    if (result && hadKey) {
      trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
    }
    return result
  }

  has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
    const result = Reflect.has(target, key)
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
      track(target, TrackOpTypes.HAS, key)
    }
    return result
  }

  ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
    track(
      target,
      TrackOpTypes.ITERATE,
      isArray(target) ? 'length' : ITERATE_KEY,
    )
    return Reflect.ownKeys(target)
  }
}

packages/reactivity/src/dep.ts

/**
 * 跟踪对响应式属性的访问。
 *
 * 该函数会检查当前正在运行的效果(effect),并将其记录为依赖项(dep),
 * 这些依赖项记录了所有依赖于该响应式属性的效果。
 *
 * @param target - 包含响应式属性的对象。
 * @param type - 定义对响应式属性的访问类型。
 * @param key - 要跟踪的响应式属性的标识符。
 */
export function track(target: object, type: TrackOpTypes, key: unknown): void {
  // 仅在启用了跟踪且存在活动效果时进行跟踪。activeSub在3.4版本及之前为activeEffect
  if (shouldTrack && activeSub) {
    // 获取目标对象的依赖映射,如果不存在则创建一个新的映射。
    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 Dep()))
      dep.map = depsMap
      dep.key = key
    }
    // 在开发模式下记录依赖项,并包含详细信息。
    if (__DEV__) {
      dep.track({
        target,
        type,
        key,
      })
    } else {
      dep.track()
    }
  }
}
/**
 * 查找与目标对象(或特定属性)关联的所有依赖项,并触发存储在其中的效果。
 *
 * @param target - 响应式对象。
 * @param type - 定义需要触发效果的操作类型。
 * @param key - 可用于指定目标对象中的特定响应式属性。
 * @param newValue - 新值,用于某些操作类型(如 SET)。
 * @param oldValue - 旧值,用于某些操作类型(如 SET)。
 * @param oldTarget - 旧的目标对象,用于某些操作类型(如 CLEAR)。
 */
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>,
): void {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // 从未被追踪过
    globalVersion++
    return
  }

  const run = (dep: Dep | undefined) => {
    if (dep) {
      if (__DEV__) {
        dep.trigger({
          target,
          type,
          key,
          newValue,
          oldValue,
          oldTarget,
        })
      } else {
        dep.trigger()
      }
    }
  }

  startBatch()

  if (type === TriggerOpTypes.CLEAR) {
    // 集合被清除
    // 触发目标对象的所有效果
    depsMap.forEach(run)
  } else {
    const targetIsArray = isArray(target)
    const isArrayIndex = targetIsArray && isIntegerKey(key)

    if (targetIsArray && key === 'length') {
      const newLength = Number(newValue)
      depsMap.forEach((dep, key) => {
        if (
          key === 'length' ||
          key === ARRAY_ITERATE_KEY ||
          (!isSymbol(key) && key >= newLength)
        ) {
          run(dep)
        }
      })
    } else {
      // 调度 SET | ADD | DELETE 的运行
      if (key !== void 0 || depsMap.has(void 0)) {
        run(depsMap.get(key))
      }

      // 调度 ARRAY_ITERATE 对于任何数字键的变化(长度已在上方处理)
      if (isArrayIndex) {
        run(depsMap.get(ARRAY_ITERATE_KEY))
      }

      // 也对 ADD | DELETE | Map.SET 运行迭代键
      switch (type) {
        case TriggerOpTypes.ADD:
          if (!targetIsArray) {
            run(depsMap.get(ITERATE_KEY))
            if (isMap(target)) {
              run(depsMap.get(MAP_KEY_ITERATE_KEY))
            }
          } else if (isArrayIndex) {
            // 新索引添加到数组 -> 长度变化
            run(depsMap.get('length'))
          }
          break
        case TriggerOpTypes.DELETE:
          if (!targetIsArray) {
            run(depsMap.get(ITERATE_KEY))
            if (isMap(target)) {
              run(depsMap.get(MAP_KEY_ITERATE_KEY))
            }
          }
          break
        case TriggerOpTypes.SET:
          if (isMap(target)) {
            run(depsMap.get(ITERATE_KEY))
          }
          break
      }
    }
  }

  endBatch()
}
ITERATE_KEY))
            }
          } else if (isArrayIndex) {
            // 新索引添加到数组 -> 长度变化
            run(depsMap.get('length'))
          }
          break
        case TriggerOpTypes.DELETE:
          if (!targetIsArray) {
            run(depsMap.get(ITERATE_KEY))
            if (isMap(target)) {
              run(depsMap.get(MAP_KEY_ITERATE_KEY))
            }
          }
          break
        case TriggerOpTypes.SET:
          if (isMap(target)) {
            run(depsMap.get(ITERATE_KEY))
          }
          break
      }
    }
  }

  endBatch()
}

面试简略回答

Vue3 的响应式系统基于 Proxy依赖收集/触发机制 实现,相比 Vue2 的 Object.defineProperty,它更高效且支持更全面的数据操作。

以下是 Vue3.2 的mini 源码实现。


一、核心实现:Proxy 代理

Vue3 使用 Proxy 对象对原始数据进行代理,拦截对数据的 读取(get)修改(set) 操作,从而实现响应式。

1. reactive() 函数
  • 作用:将普通对象转换为响应式对象。
  • 实现原理
    function reactive(target) {
      return new Proxy(target, {
        get(target, key, receiver) {
          track(target, key); // 依赖收集
          return Reflect.get(target, key, receiver);
        },
        set(target, key, value, receiver) {
          const result = Reflect.set(target, key, value, receiver);
          trigger(target, key); // 触发更新
          return result;
        }
      });
    }
    

使用 Reflect 而不是 target[key] 的主要原因:

  1. 与 Proxy 的方法一一对应,简化拦截逻辑。
  2. 通过 receiver 参数维护正确的上下文,避免使用 target[key] 导致的this 指向原始对象而非代理对象,导致的数据不一致。
2. ref() 函数
  • 作用:将基本类型值或对象包装为响应式引用。
  • 实现原理
    function ref(value) {
      const refObj = {
        get value() {
          track(refObj, 'value'); // 依赖收集
          return value;
        },
        set value(newValue) {
          value = newValue;
          trigger(refObj, 'value'); // 触发更新
        }
      };
      return refObj;
    }
    

二、依赖收集与触发更新

Vue3 通过 依赖关系图 管理数据与副作用(如组件渲染函数)的关联。

1. 依赖收集(Track)
  • 当访问响应式对象的属性时,触发 track(),记录当前正在运行的副作用(如组件的渲染函数)。

  • 数据结构

    const targetMap = new WeakMap(); // 以对象为键,存储依赖关系
    
    function track(target, key) {
      if (activeEffect) {
        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()));
        }
        dep.add(activeEffect); // 将当前副作用添加到依赖集合
      }
    }
    
2. 触发更新(Trigger)
  • 当修改响应式数据时,触发 trigger(),通知所有关联的副作用重新执行。
    function trigger(target, key) {
      const depsMap = targetMap.get(target);
      if (!depsMap) return;
      const effects = depsMap.get(key);
      effects && effects.forEach(effect => effect()); // 执行所有依赖的副作用
    }
    

在这里插入图片描述

上图来源:小满zs


三、副作用管理(Effect)

Vue3 通过 effect() 函数定义副作用(如组件渲染、计算属性、侦听器等),并在数据变化时自动重新运行。

1. effect() 函数
  • 作用:定义响应式数据的副作用(如组件渲染函数)。
  • 示例
    let activeEffect;
    function effect(fn) {
      const wrapper = () => {
        activeEffect = wrapper;
        fn(); // 执行副作用函数
        activeEffect = null;
      };
      wrapper(); // 立即执行一次以收集依赖
    }
    
2. 组件渲染流程
// 组件渲染示例
effect(() => {
  render(component); // 组件渲染函数
});

四、响应式系统的优势

1. 全面拦截操作
  • Proxy 支持拦截对象的所有操作(增删改查、数组方法等),无需像 Vue2 那样通过 Vue.set/Vue.delete 手动处理。
    const obj = reactive({ a: 1 });
    obj.b = 2; // 自动触发响应式(Vue2 无法检测)
    
2. 深层响应式
  • reactive() 会递归地将嵌套对象转换为响应式。
  • 可通过 shallowReactive() 创建浅层响应式对象。
3. 性能优化
  • 依赖收集基于 WeakMap,避免内存泄漏。
  • 批量异步更新(通过调度器)减少重复计算。

五、对比 Vue2 的改进

特性Vue2(Object.defineProperty)Vue3(Proxy)
拦截能力无法检测属性新增/删除支持所有操作
数组处理需重写数组方法(push/pop等)直接拦截数组索引和 length 变化
性能递归遍历对象属性,初始化慢按需代理,初始化更快
内存占用每个属性创建 Dep 实例依赖存储在 WeakMap,内存占用更低

六、示例:响应式数据更新流程

// 1. 创建响应式对象
const state = reactive({ count: 0 });

// 2. 定义副作用(模拟组件渲染)
effect(() => {
  console.log('Count updated:', state.count);
});

// 3. 修改数据,触发更新
state.count++; // 输出 "Count updated: 1"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秀秀_heo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值