Vue课代表,今天复习响应式(三)

前景回顾

Vue课代表,今天复习响应式(一)

Vue课代表,今天复习响应式(二)

各位看官,上回书说到,咱们Vue3源码中实现响应式用到了track函数trigger函数,两者分别对应着依赖收集和依赖触发。

那么这次我们要了解什么呢?那就继续看看Vue3的田地里面挖出的宝藏源码吧!

额外的知识点,Reflect

来看看下面这个文章吧 ↓↓↓↓↓↓

JS课代表,今天复习反射(Reflect)

响应式的触发(Handlers)

警告:以下内容第一次看可能会有点懵,没点代码基础会犯困

baseHandlers主要处理的是数组和对象。 它包含了一些基本的操作,比如获取属性值(get)、设置属性值(set)、删除属性(deleteProperty)等。这些操作都会触发Vue的响应式系统,使得当数据发生变化时,视图也会相应地更新。

collectionHandlers则是用来处理Map、Set、WeakMap、WeakSet等数据结构。 这些数据结构在JavaScript中是非常常用的,所以Vue也提供了对它们的响应式处理。

baseHandlers

要看Vue3对proxy对象进行了什么操作,主要还是看MutableReactiveHandler和BaseReactiveHandler两个类,而MutableReactiveHandler又继承了BaseReactiveHandler。

BaseReactiveHandler

在 Vue3 的源码中,BaseReactiveHandler 是一个非常重要的类,主要作用是拦截对象的属性访问。

关键部分代码

ts 复制代码
class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _shallow = false,
  ) {}

  get(target: Target, key: string | symbol, receiver: object) {
    // ...省略部分代码...
    const res = Reflect.get(target, key, receiver);
    // ...省略部分代码...
    if (!isReadonly) {
      // 触发相应收集track函数
      track(target, TrackOpTypes.GET, key)
    }
    // ...省略部分代码...
    return res;
  }
}

get 方法

get 方法用于拦截对象的读取属性操作。它首先检查被访问的属性是否是一些特殊的标志(如 IS_REACTIVEIS_READONLYIS_SHALLOWRAW),然后根据不同的标志返回不同的值。如果被访问的属性不是这些特殊的标志,那么它会使用 Reflect.get() 来获取属性值,并根据是否只读、是否浅层响应等条件进行额外的处理。

源码解析(可跳过)

源码如下:

ts 复制代码
// BaseReactiveHandler 是 Vue3 中用于实现响应式系统的基础类
class BaseReactiveHandler implements ProxyHandler<Target> {
  // 构造函数接收两个参数,分别表示是否只读和是否浅层响应
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _shallow = false,
  ) {}

  // get 方法用于拦截对象的读取属性操作
  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      shallow = this._shallow
    // 如果读取的属性是 IS_REACTIVE,返回 !isReadonly
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } 
    // 如果读取的属性是 IS_READONLY,返回 isReadonly
    else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } 
    // 如果读取的属性是 IS_SHALLOW,返回 shallow
    else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } 
    // 如果读取的属性是 RAW,并且 receiver 是 reactive 或 readonly 的代理对象,返回 target
    else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? shallow
              ? shallowReadonlyMap
              : readonlyMap
            : shallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver 不是 reactive 代理,但具有相同的原型
        // 这意味着 receiver 是 reactive 代理的用户代理
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // 提前返回 undefined
      return
    }

    const targetIsArray = isArray(target)

    if (!isReadonly) {
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    const res = Reflect.get(target, key, receiver)

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

    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref 解包 - 对于 Array + integer key,跳过解包。
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
      // 将返回值也转换为代理。我们在这里进行 isObject 检查
      // 以避免无效值警告。此外,需要在此处延迟访问 readonly
      // 和 reactive,以避免循环依赖。
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

MutableReactiveHandler

MutableReactiveHandler 用于处理非只读的响应式对象,这个类主要负责拦截对象的属性访问和修改操作,并在必要时触发依赖更新。

关键部分代码

set 方法是 MutableReactiveHandler 的关键部分之一,它用于拦截对象的属性修改操作。当我们试图修改一个响应式对象的属性时,这个方法会被调用。

deleteProperty 方法用于拦截对象的属性删除操作。当我们试图删除一个响应式对象的属性时,这个方法会被调用。

hasownKeys 方法分别用于拦截对象的属性检查操作和获取所有键的操作。

由于代码逻辑有点长,还是详细看看源码吧!

源码解析

以下是带有详细中文注释的 MutableReactiveHandler 类的源码:

ts 复制代码
// MutableReactiveHandler 类继承自 BaseReactiveHandler
class MutableReactiveHandler extends BaseReactiveHandler {
  // 构造函数接收一个参数,表示是否浅层响应
  constructor(shallow = false) {
    // 调用父类构造函数,设置 _isReadonly 为 false,_shallow 为传入的值
    super(false, shallow)
  }

  // set 方法用于拦截对象的修改属性操作
  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    // 获取旧值
    let oldValue = (target as any)[key]
    // 如果不是浅层响应
    if (!this._shallow) {
      // 检查旧值是否只读
      const isOldValueReadonly = isReadonly(oldValue)
      // 如果新值不是浅层的,且不是只读的,将旧值和新值转换为原始对象
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      // 如果目标不是数组,且旧值是 ref,新值不是 ref
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        // 如果旧值是只读的,返回 false
        if (isOldValueReadonly) {
          return false
        } else {
          // 否则,将新值赋给旧值的 value 属性,返回 true
          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, 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)
      }
    }
    // 返回 Reflect.set 的结果
    return result
  }

  // deleteProperty 方法用于拦截对象的删除属性操作
  deleteProperty(target: object, key: string | symbol): boolean {
    // 检查目标是否有该属性
    const hadKey = hasOwn(target, key)
    // 获取旧值
    const oldValue = (target as any)[key]
    // 使用 Reflect.deleteProperty 删除属性
    const result = Reflect.deleteProperty(target, key)
    // 如果删除成功且目标有该属性,触发删除操作的依赖更新
    if (result && hadKey) {
      trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
    }
    // 返回 Reflect.deleteProperty 的结果
    return result
  }

  // has 方法用于拦截对象的属性检查操作
  has(target: object, key: string | symbol): boolean {
    // 使用 Reflect.has 检查属性
    const result = Reflect.has(target, key)
    // 如果键不是符号,或者不是内置的符号,进行依赖跟踪
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
      track(target, TrackOpTypes.HAS, key)
    }
    // 返回 Reflect.has 的结果
    return result
  }

  // ownKeys 方法用于拦截对象的获取所有键的操作
  ownKeys(target: object): (string | symbol)[] {
    // 进行依赖跟踪
    track(
      target,
      TrackOpTypes.ITERATE,
      isArray(target) ? 'length' : ITERATE_KEY,
    )
    // 返回 Reflect.ownKeys 的结果
    return Reflect.ownKeys(target)
  }
}

collectionHandlers

collectionHandlers的代码写法和baseHandlers代码写法有明显差别,在我们收起所有函数内容后,可以看到一系列Set(WeekSet)Map(WeekMep)的方法拦截。

而抛出来的函数主要有以下几个:

  • mutableCollectionHandlers 为集合类型创建响应式遍历器。
  • shallowCollectionHandlers 为集合类型创建浅层响应式遍历器。
  • readonlyCollectionHandlers 为集合类型创建只读响应式遍历器。
  • shallowReadonlyCollectionHandlers 为集合类型创建浅层只读响应式遍历器。

通过代码我们发现,他们都调用的createInstrumentationGetter函数,它的主要作用是创建一个用于获取集合元素的函数。

这个函数接收两个参数:isReadonly(是否是只读响应式)和shallow(是否是浅层响应式)。根据这两个参数的值,函数会选择对应的处理器(instrumentations),源码如下:

ts 复制代码
// 创建处理器
const [
  mutableInstrumentations,
  readonlyInstrumentations,
  shallowInstrumentations,
  shallowReadonlyInstrumentations,
] = /* #__PURE__*/ createInstrumentations()

// 创建一个用于获取集合元素的函数
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  // 根据参数选择对应的处理器
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
      ? readonlyInstrumentations
      : mutableInstrumentations

  // 返回一个 getter 函数
  return (
    target: CollectionTypes,
    key: string | symbol,
    receiver: CollectionTypes,
  ) => {
    // 如果 key 是特殊的标志,则返回对应的值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.RAW) {
      return target
    }

    // 否则,从处理器或目标对象中获取值
    return Reflect.get(
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver,
    )
  }
}

那为什么只处理了一个get函数呢?

因为对于集合类型(如SetMap等),读取操作和修改操作都是通过调用方法进行的,所以只需要捕获其get方法即可。

例如,我们可以通过map.get(key)来获取Map中的元素,通过set.add(value)来向Set中添加元素。因此,只需要代理get方法,就可以对这些操作进行拦截和处理。

那么根据处理器,我们看到了createInstrumentations()这个函数抛出了4个核心的对象,这4个对象的内部实现就是Vue3中对于集合类型的劫持。

在看源码之前,我们重新认识一下Vue3是如何处理集合类型的响应收集和更新

  • 创建响应式对象 :当我们使用reactive或者readonly函数创建一个Map对象的响应式版本时,Vue3会使用Proxy对象对其进行包装。这个Proxy对象的getset等操作会被重写,以便在执行这些操作时进行额外的处理。
  • 劫持集合方法 :在createInstrumentations函数中,Vue3为Map对象的各种方法(如getsethasdelete等)创建了对应的处理器(instrumentations)。这些处理器在执行相应的操作时,会同时触发依赖收集(track)和派发更新(trigger)。
  • 依赖收集 :当我们在副作用函数(effect)中访问Map对象的某个键时,会触发Proxy对象的get操作。在这个操作中,Vue3会调用track函数进行依赖收集。这意味着,Vue3会记住当前的副作用函数依赖于这个键。
  • 派发更新 :当我们修改Map对象的某个键时,会触发Proxy对象的set操作。在这个操作中,Vue3会调用trigger函数派发更新。这意味着,Vue3会找出所有依赖于这个键的副作用函数,并重新执行它们,以使它们能够获取到最新的值。

以下源码,请品尝:

createInstrumentations源码(可跳过)

ts 复制代码
// 创建一组用于处理集合类型的方法
function createInstrumentations() {
  // 可变集合的处理器
  const mutableInstrumentations: Record<string, Function | number> = {
    // 获取集合中的元素
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    // 获取集合的大小
    get size() {
      return size(this as unknown as IterableCollections)
    },
    // 检查集合中是否存在某个元素
    has,
    // 向集合中添加元素
    add,
    // 在集合中设置元素
    set,
    // 从集合中删除元素
    delete: deleteEntry,
    // 清空集合
    clear,
    // 遍历集合
    forEach: createForEach(false, false),
  }

  // 浅层集合的处理器
  const shallowInstrumentations: Record<string, Function | number> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, false, true)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, true),
  }

  // 只读集合的处理器
  const readonlyInstrumentations: Record<string, Function | number> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    // 下面的方法都会抛出错误,因为只读集合不能进行修改
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, false),
  }

  // 浅层只读集合的处理器
  const shallowReadonlyInstrumentations: Record<string, Function | number> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    // 下面的方法都会抛出错误,因为只读集合不能进行修改
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, true),
  }

  // 需要创建迭代器方法的方法名列表
  const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
  iteratorMethods.forEach(method => {
    // 为每种处理器创建对应的迭代器方法
    mutableInstrumentations[method as string] = createIterableMethod(
      method,
      false,
      false,
    )
    readonlyInstrumentations[method as string] = createIterableMethod(
      method,
      true,
      false,
    )
    shallowInstrumentations[method as string] = createIterableMethod(
      method,
      false,
      true,
    )
    shallowReadonlyInstrumentations[method as string] = createIterableMethod(
      method,
      true,
      true,
    )
  })

  // 返回创建好的处理器
  return [
    mutableInstrumentations,
    readonlyInstrumentations,
    shallowInstrumentations,
    shallowReadonlyInstrumentations,
  ]
}

参考文献

对于一些理解,这篇文章的作者比我写的好:

本章小结

个人觉得这篇文章还是写的比较浅显,很多内容理解的比较生硬。

待俺去再详细看看,继续补充一些例子上来(下次一定)。

结束

响应式的基本概念基本上在这三篇文章中讲了部分,后续作者还会深入学习,持续补充。

谢谢你的观看。

相关推荐
小镇程序员2 小时前
vue2 src自定义事件
前端·javascript·vue.js
AlgorithmAce4 小时前
Live2D嵌入前端页面
前端
nameofworld4 小时前
前端面试笔试(六)
前端·javascript·面试·学习方法·递归回溯
前端fighter4 小时前
js基本数据新增的Symbol到底是啥呢?
前端·javascript·面试
GISer_Jing5 小时前
从0开始分享一个React项目:React-ant-admin
前端·react.js·前端框架
川石教育5 小时前
Vue前端开发子组件向父组件传参
前端·vue.js·前端开发·vue前端开发·vue组件传参
GISer_Jing5 小时前
Vue前端进阶面试题目(二)
前端·vue.js·面试
乐闻x6 小时前
Pinia 实战教程:构建高效的 Vue 3 状态管理系统
前端·javascript·vue.js
weixin_431449686 小时前
web组态软件
前端·物联网·低代码·编辑器·组态
橘子味小白菜6 小时前
el-table的树形结构后端返回的id没有唯一键怎么办
前端·vue.js