找回密码
 立即注册
首页 业界区 业界 [vue3] Vue3源码阅读笔记 reactivity - baseHandlers ...

[vue3] Vue3源码阅读笔记 reactivity - baseHandlers

孟茹云 2025-6-6 15:58:10


源码位置:https://github.com/vuejs/core/blob/main/packages/reactivity/src/baseHandlers.ts
baseHandler用于处理对象、数组类型的getter和setter。
这个文件中主要有两个函数,和三个类。
arrayInstrucmentations和hasOwnProperty这两个函数主要起到辅助作用;
3个类:

  • BaseReactiveHandler:负责处理getter;
  • MutableReactiveHandler和ReadonlyReactiveHandler:负责处理setter;
graph LR        A[ProxyHandler] --> B[BaseReactiveHandler]        B --> C[MutableReactiveHandler] & D[ReadonlyReactiveHandler]依赖

依赖比较零碎,大致分为以下几种:

  • 用于判断数据类型;
  • Vue内置的Flag或Type,用来标记对象或者操作的类型;
  • 暂停与重置依赖追踪和任务调度的方法;
  • 其它零碎的比如:

    • warning:在控制台输出警告信息;
    • makeMap:传入一个用,分隔的多个key组成的字符串,返回一个has函数用于检查后续传入的key是否存在于一开始传入的字符串中。

arrayInstrucmentations

arrayInstrumentations这个对象用于记录一些处理过的数组方法(拦截操作),通过createArrayInstrumentations构建后大概长这样:
  1. {
  2.     ...
  3.     'push': function(this, ...args){...},
  4.     'indexOf': function(this, ...args){...},
  5.         ...
  6. }
复制代码
拦截这些方法的原因

  • ['includes', 'indexOf', 'lastIndexOf']:数组中可能包含响应式对象,这几个方法是需要比较数组元素的,直接比较可能会出错,因此需要拦截这些方法,在比较的过程中考虑使用toRaw转成原始对象进行比较。
  • ['push', 'pop', 'shift', 'unshift', 'splice']:这些方法会改变数组的长度length属性,从而触发与length属性相关的effect,而effect中如果又有这些方法,那么就会导致死循环。因此,这些方法需要被拦截做特殊处理,在执行这些方法的时候要暂停依赖的追踪和调度。
    相关的issue是:fix(reactivity): some mutation methods of Array cause infinite recursion by unbyte · Pull Request #2138 · vuejs/core (github.com)
    1.png


源码与注释
  1. const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
  2. function createArrayInstrumentations() {
  3.   const instrumentations: Record<string, Function> = {}
  4.   // 给需要处理可能包含响应式值的数组方法增加拦截逻辑
  5.   ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  6.     instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
  7.       // 将 this 转换为原始数组
  8.       const arr = toRaw(this) as any
  9.       
  10.       // 追踪数组每个元素的 GET 操作
  11.       for (let i = 0, l = this.length; i < l; i++) {
  12.         track(arr, TrackOpTypes.GET, i + '')
  13.       }
  14.       
  15.       // 先使用原始参数(可能是响应式的)运行原方法
  16.       const res = arr[key](...args)
  17.       
  18.       // 如果结果是 -1 或 false,说明没有找到或不匹配,再次使用原始值运行一次
  19.       if (res === -1 || res === false) {
  20.         return arr[key](...args.map(toRaw))
  21.       } else {
  22.         return res
  23.       }
  24.     }
  25.   })
  26.   // 拦截会改变数组长度的方法,避免长度变化被追踪,从而防止出现无限循环的问题 (#2137)
  27.   ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  28.     instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
  29.       // 暂停追踪
  30.       pauseTracking()
  31.       // 暂停调度
  32.       pauseScheduling()
  33.       
  34.       // 使用原始数组运行原方法
  35.       const res = (toRaw(this) as any)[key].apply(this, args)
  36.       
  37.       // 重置调度
  38.       resetScheduling()
  39.       // 重置追踪
  40.       resetTracking()
  41.       
  42.       return res
  43.     }
  44.   })
  45.   
  46.   return instrumentations
  47. }
复制代码
hasOwnProperty

Vue对hasOwnProperty做了特殊处理,主要在于处理以下问题:

  • 确保需要检查的key只能是Symbol类型或string类型,如果是其它,则通过String()转为字符串;
  • 使用toRaw在原始对象上查询key是否存在;
  • 使用track追踪key;
源码
  1. function hasOwnProperty(this: object, key: unknown) {
  2.   // #10455 hasOwnProperty may be called with non-string values
  3.   if (!isSymbol(key)) key = String(key)
  4.   const obj = toRaw(this)
  5.   track(obj, TrackOpTypes.HAS, key)
  6.   return obj.hasOwnProperty(key as string)
  7. }
复制代码
BaseReactiveHandler

这个类主要负责配置getter,当reactiveAPI包装的响应式对象的某个key被读取时,会触发这里的getter:

  • 如果读取的key是内置的ReactiveFlags,返回相应的值;
  • 如果target是一个数组,那么需要应用上述arrayInstrucmentations记录的处理过的数组;
  • 如果key是hasOwnProperty,返回上述特殊处理过的hasOwnProperty;
  • 对key记录依赖。
  1. class BaseReactiveHandler implements ProxyHandler<Target> {
  2.   constructor(
  3.     protected readonly _isReadonly = false, // 是否只读
  4.     protected readonly _isShallow = false, // 是否浅层响应式
  5.   ) {}
  6.   get(target: Target, key: string | symbol, receiver: object) {
  7.     const isReadonly = this._isReadonly,
  8.       isShallow = this._isShallow
  9.     // 处理 ReactiveFlags 特殊标志
  10.     if (key === ReactiveFlags.IS_REACTIVE) {
  11.       // 判断目标是否是响应式的
  12.       return !isReadonly
  13.     } else if (key === ReactiveFlags.IS_READONLY) {
  14.       // 判断目标是否是只读的
  15.       return isReadonly
  16.     } else if (key === ReactiveFlags.IS_SHALLOW) {
  17.       // 判断目标是否是浅层响应的
  18.       return isShallow
  19.     } else if (key === ReactiveFlags.RAW) {
  20.       // 处理 RAW 标志
  21.       if (
  22.         receiver ===
  23.           (isReadonly
  24.             ? isShallow
  25.               ? shallowReadonlyMap
  26.               : readonlyMap
  27.             : isShallow
  28.               ? shallowReactiveMap
  29.               : reactiveMap
  30.           ).get(target) ||
  31.         // receiver 不是响应式代理,但具有相同的原型
  32.         // 这意味着 receiver 是响应式代理的用户代理
  33.         Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
  34.       ) {
  35.         return target
  36.       }
  37.       // 提前返回 undefined
  38.       return
  39.     }
  40.     const targetIsArray = isArray(target)
  41.     // 处理非只读的情况
  42.     if (!isReadonly) {
  43.       if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
  44.         // 对于数组的特殊方法,使用上述的方法拦截处理
  45.         return Reflect.get(arrayInstrumentations, key, receiver)
  46.       }
  47.       if (key === 'hasOwnProperty') {
  48.         // 特殊处理 hasOwnProperty 方法
  49.         return hasOwnProperty
  50.       }
  51.     }
  52.     // 默认的 Reflect.get 操作
  53.     const res = Reflect.get(target, key, receiver)
  54.     // 处理内置符号和非可追踪的键
  55.     if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
  56.       return res
  57.     }
  58.     // 处理非只读情况,执行追踪操作
  59.     if (!isReadonly) {
  60.       track(target, TrackOpTypes.GET, key)
  61.     }
  62.     // 处理浅层响应情况
  63.     if (isShallow) {
  64.       return res
  65.     }
  66.     // 处理 Ref 类型的值
  67.     if (isRef(res)) {
  68.       // 如果是数组并且键是整数,则跳过 unwrap
  69.       return targetIsArray && isIntegerKey(key) ? res : res.value
  70.     }
  71.     // 处理对象类型的值,将返回的值转化为代理对象
  72.     if (isObject(res)) {
  73.       // 将返回的值转换为代理对象,避免循环依赖
  74.       return isReadonly ? readonly(res) : reactive(res)
  75.     }
  76.     return res
  77.   }
  78. }
复制代码
MutableReactiveHandler

这个类实现了对set、deleteProperty、has、ownKeys的拦截
  1. class MutableReactiveHandler extends BaseReactiveHandler {
  2.   constructor(isShallow = false) {
  3.     super(false, isShallow) // 调用父类构造函数,设置只读标志为 false
  4.   }
  5.   set(
  6.     target: object,
  7.     key: string | symbol,
  8.     value: unknown,
  9.     receiver: object,
  10.   ): boolean {
  11.     let oldValue = (target as any)[key] // 获取目标对象中原有的值
  12.     if (!this._isShallow) {
  13.       // 如果不是浅层响应,进行深层处理
  14.       const isOldValueReadonly = isReadonly(oldValue) // 判断原有值是否是只读的
  15.       if (!isShallow(value) && !isReadonly(value)) {
  16.         oldValue = toRaw(oldValue) // 获取原有值的原始对象
  17.         value = toRaw(value) // 获取新值的原始对象
  18.       }
  19.       // 如果原有值是 ref 类型并且新值不是 ref 类型
  20.       if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
  21.         if (isOldValueReadonly) {
  22.           // 如果原有值是只读的,返回 false
  23.           return false
  24.         } else {
  25.           oldValue.value = value // 更新 ref 的值
  26.           return true
  27.         }
  28.       }
  29.     } else {
  30.       // 在浅层模式中,直接设置对象,不考虑其是否为响应式
  31.     }
  32.     // 判断目标对象是否之前已经有这个键
  33.     const hadKey =
  34.       isArray(target) && isIntegerKey(key)
  35.         ? Number(key) < target.length
  36.         : hasOwn(target, key)
  37.     const result = Reflect.set(target, key, value, receiver) // 使用 Reflect 设置值
  38.     // 判断target是否是实际被修改的对象
  39.     if (target === toRaw(receiver)) {
  40.       if (!hadKey) {
  41.         // 如果之前没有这个键,触发 ADD 操作
  42.         trigger(target, TriggerOpTypes.ADD, key, value)
  43.       } else if (hasChanged(value, oldValue)) {
  44.         // 如果键已经存在且新值不同,触发 SET 操作
  45.         trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  46.       }
  47.     }
  48.     return result
  49.   }
  50.   deleteProperty(target: object, key: string | symbol): boolean {
  51.     const hadKey = hasOwn(target, key) // 判断目标对象是否有这个键
  52.     const oldValue = (target as any)[key] // 获取原有值
  53.     const result = Reflect.deleteProperty(target, key) // 使用 Reflect 删除属性
  54.     if (result && hadKey) {
  55.       // 如果删除成功且目标对象之前有这个键,触发 DELETE 操作
  56.       trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  57.     }
  58.     return result
  59.   }
  60.   has(target: object, key: string | symbol): boolean {
  61.     const result = Reflect.has(target, key) // 使用 Reflect 检查是否存在该键
  62.     if (!isSymbol(key) || !builtInSymbols.has(key)) {
  63.       // 如果键不是内置符号,追踪 HAS 操作
  64.       track(target, TrackOpTypes.HAS, key)
  65.     }
  66.     return result
  67.   }
  68.   ownKeys(target: object): (string | symbol)[] {
  69.     // 追踪 ITERATE 操作,用于获取对象的所有键
  70.     track(
  71.       target,
  72.       TrackOpTypes.ITERATE,
  73.       isArray(target) ? 'length' : ITERATE_KEY,
  74.     )
  75.     return Reflect.ownKeys(target) // 使用 Reflect 获取对象的所有键
  76.   }
  77. }
复制代码
解析:set里的trigger时机:
  1. // 判断target是否是实际被修改的对象
  2. if (target === toRaw(receiver)) {
  3.   if (!hadKey) {
  4.     // 如果之前没有这个键,触发 ADD 操作
  5.     trigger(target, TriggerOpTypes.ADD, key, value)
  6.   } else if (hasChanged(value, oldValue)) {
  7.     // 如果键已经存在且新值不同,触发 SET 操作
  8.     trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  9.   }
  10. }
复制代码
如果target !== toRaw(receiver)说明此时不是在对象上修改它本身的属性,而是通过原型链上的其它对象。这种情况不会触发更新。
ReadonlyReactiveHandler

这个类比较简单,主要是拦截set和deleteProperty这两个会改变对象的操作。
开发模式下会在控制台输出警告。
注意set和deleteProperty操作会返回true,这是为了符合Proxy规范:即使某些操作被拦截并不实际改变对象的状态,仍然需要返回一个布尔值以指示操作的成功或失败。
  1. class ReadonlyReactiveHandler extends BaseReactiveHandler {
  2.   constructor(isShallow = false) {
  3.     super(true, isShallow) // 调用父类构造函数,将 isReadonly 设置为 true,表示对象是只读的
  4.   }
  5.   set(target: object, key: string | symbol, value: unknown): boolean {
  6.     if (__DEV__) {
  7.       warn(
  8.         `Set operation on key "${String(key)}" failed: target is readonly.`,
  9.         target,
  10.       )
  11.     }
  12.     return true // 返回 true,表示设置操作被忽略
  13.   }
  14.   deleteProperty(target: object, key: string | symbol): boolean {
  15.     if (__DEV__) {
  16.       warn(
  17.         `Delete operation on key "${String(key)}" failed: target is readonly.`,
  18.         target,
  19.       )
  20.     }
  21.     return true // 返回 true,表示删除操作被忽略
  22.   }
  23. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册