【Vue3源码学习】— CH2.3 MutableReactiveHandler详解

1. mutableHandlers是什么?

回顾一下在reactive.ts章节,当我们调用Vue的reactive方法时,在Vue底层实际上是创建并返回了一个proxy对象,创建这个代理对象时,我们传入的处理器是 mutableHandlers:

export function reactive(target: object) {
  if (isReadonly(target)) {
    return target
  }

  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}

mutableHandlers 实际上是 MutableReactiveHandler 类的一个实例:

//reactive.ts文件中引用:
import { mutableHandlers } from './baseHandlers'

//baseHandler.ts文件定义:
export const mutableHandlers: ProxyHandler<object> =
  /*#__PURE__*/ new MutableReactiveHandler()

这表明,每当我们将一个对象通过 reactive 转换成响应式对象时,实际上就是在应用 MutableReactiveHandler 类提供的逻辑。

2. MutableReactiveHandler 类的定义

MutableReactiveHandler 类在 Vue 3 的响应式系统中扮演着核心角色,它继承BaseReacriverHandler类,并且扩展了处理非只读响应式对象的各种操作,包括属性的设置(set)、删除(deleteProperty)、存在性检查(has)和键的枚举(ownKeys)等。

3. 核心方法解析

class MutableReactiveHandler extends BaseReactiveHandler {
  /**
  * 构造函数接受一个isShallow参数,用于决定创建的响应式对象是否为浅响应式。这影响着对象内部属性的响应式行为。
  */
  constructor(isShallow = false) {
    super(false, isShallow)
  }

  /**
   * set方法拦截对响应式对象属性的赋值操作。
   * 它首先比较新值和旧值,如果有必要,会进行相应的更新,并触发相应的副作用(如视图更新)。
   * 特别地,如果属性值从ref变为非ref,或相反,这个方法会进行特殊处理。
   */
  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    let oldValue = (target as any)[key]
  
    if (!this._isShallow) {
      /**
       * 深响应式对象的处理逻辑
       */
      // 1.处理旧值和新值
      // 处理旧值和新值:首先判断当前属性的旧值是否是只读的
      const isOldValueReadonly = isReadonly(oldValue)
      if (!isShallow(value) && !isReadonly(value)) {
        /**
         * 1.2 转换旧值和新值为原始对象:
         * 如果新值既不是浅响应式的也不是只读的,那么会将旧值和新值都转换为它们的原始对象(如果它们是响应式的或只读的)。
         * 这一步确保在对比和设置属性值时,我们操作的是原始数据,而不是响应式代理。
         */
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      // 2.处理ref值
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        /**
         * 2.1 条件检查:这一部分的逻辑仅在目标不是数组、旧值是一个ref且新值不是ref的情况下执行。
         * 这是因为Vue允许你将一个ref直接赋值给对象的属性,但如果旧值是ref而新值不是,需要特殊处理。
         */
        if (isOldValueReadonly) {
          /**
           * 2.2 只读ref的处理:如果旧值是一个只读的ref,那么不允许修改它的值,因此函数返回false表示设置操作失败。
           */
          return false
        } else {
          /**
           * 2.3 更新ref的值:如果旧值是一个普通的ref(不是只读的),那么将直接更新这个ref的value属性为新值,并返回true表示设置操作成功。
           */
          oldValue.value = value
          return true
        }
      }
    } else {
      /**
       * 当一个响应式对象处于浅响应模式时,对其属性进行设置操作,设置的值将不会自动转换为响应式对象。
       * 即,不论你设置的值原本是否是响应式的,它都将按原样(as-is)被设置到对象上,Vue不会对这个值进行深层的响应式转换。
       */
    }

    //3. 判断属性是否已存在
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 3.1 设置属性值:使用Reflect.set方法来实际上设置目标对象的属性值
    const result = Reflect.set(target, key, value, receiver)
    /**
     * 属性值发生变化时触发更新
     * toRaw函数用来获取响应式对象背后的原始对象
     * 这个检查确保只有当操作的目标对象就是接收对象的原始对象时,才会触发更新。这是为了避免在原型链上误触发更新
     */
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        //如果属性之前不存在(对于新添加的属性),则使用trigger函数触发ADD操作的更新。
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        //如果属性已存在且新值与旧值不相同(使用hasChanged函数检查),则触发SET操作的更新。
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    //返回结果:告诉调用者属性是否成功被设置
    return result
  }

  /**
   * deleteProperty方法拦截对响应式对象属性的删除操作。如果属性存在且成功删除,会触发副作用。
   */
  deleteProperty(target: object, key: string | symbol): boolean {
    const hadKey = hasOwn(target, key)
    const oldValue = (target as any)[key]
    const result = Reflect.deleteProperty(target, key)
    if (result && hadKey) {
      trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
    }
    return result
  }

  /**
   * has方法拦截属性存在性的检查操作。这个操作也会被用于依赖追踪,以便在未来这个属性被添加时能触发更新。
   */
  has(target: object, key: string | symbol): boolean {
    const result = Reflect.has(target, key)
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
      track(target, TrackOpTypes.HAS, key)
    }
    return result
  }
  /**
   * ownKeys方法拦截对象键的枚举操作。这在进行for...in循环或Object.keys调用时特别有用,同样会进行依赖追踪。
   */
  ownKeys(target: object): (string | symbol)[] {
    track(
      target,
      TrackOpTypes.ITERATE,
      isArray(target) ? 'length' : ITERATE_KEY,
    )
    return Reflect.ownKeys(target)
  }
}

4. 举个例子

当我们通过 reactive 方法创建响应式对象并对其属性进行操作时,MutableReactiveHandler 的各种方法将被调用来确保响应式行为的正确执行:

const reactiveObject = reactive({ a: 1 });
reactiveObject.a = 2;

在这个过程中:

4.1 关于target和receiver:

target是原始的对象,即通过reactive包装之前的对象,这里是{ a: 1 }。
receiver是reactiveObject,即reactive({ a: 1 })的返回值,一个代理对象,它代表了target的响应式版本。

4.2 获取旧值:

当尝试通过reactiveObject.a = 2;设置属性时,set方法会被调用。此时,会从target中获取属性a的旧值,这里旧值是1。

4.3 转换为原始对象:

如果旧值或新值是通过ref或reactive包装的响应式对象,toRaw会将其转换回原始对象。但在这个例子中,旧值1和新值2都是普通数值,没有使用ref包装,所以这一步跳过。

4.4 处理ref值:

这个步骤主要是为了处理当目标对象的某个属性是一个ref,而新设置的值不是ref的情况。但在这个例子中,既没有涉及到ref,所以这部分逻辑不会被执行。

4.5 判断属性是否已存在并设置新值:

通过Reflect.set直接设置属性值,这一步不仅会更新对象的属性,还会检查这个属性是否之前存在于对象中。
hadKey用于判断属性在操作之前是否存在,这对于后续决定是触发ADD还是SET类型的更新很重要。

4.6 触发更新:

根据属性是否新添加以及值是否改变,使用trigger函数触发相应的更新。在这个例子中,因为a属性在target上已存在,且新值与旧值不同,所以会触发TriggerOpTypes.SET类型的更新。

5. 小结

MutableReactiveHandler 是 Vue 3 响应式系统的核心组成之一,它通过 Proxy API 拦截对响应式对象的操作,并以此实现数据到视图的自动响应。通过继承 BaseReactiveHandler 并扩展其功能,Vue 能够提供一个强大而灵活的响应式系统,支持开发者构建高效且响应迅速的 Web 应用。

在这里插入图片描述

相关推荐

  1. Vue3学习】— CH3.4 baseCreateRenderer 详解

    2024-03-23 13:16:01       12 阅读
  2. Vue3学习】— CH3.5 renderer 详解

    2024-03-23 13:16:01       10 阅读
  3. Vue3学习】— CH3.2 VNode解析(上)

    2024-03-23 13:16:01       18 阅读
  4. 学习 Vue 3

    2024-03-23 13:16:01       43 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-23 13:16:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-23 13:16:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-23 13:16:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-23 13:16:01       20 阅读

热门阅读

  1. C# 线程锁使用

    2024-03-23 13:16:01       19 阅读
  2. Android输入法相关(二)

    2024-03-23 13:16:01       20 阅读
  3. 怎样保持SSH长时连接不断开(客户机)

    2024-03-23 13:16:01       22 阅读
  4. 服务器硬防和软防是什么?

    2024-03-23 13:16:01       20 阅读
  5. Linux 文件系统:动静态库

    2024-03-23 13:16:01       25 阅读
  6. Keepalived 踩坑

    2024-03-23 13:16:01       27 阅读
  7. ArrayList的常用方法

    2024-03-23 13:16:01       18 阅读