vue3 reactive源码解析

Vue3中的数据双向绑定原理主要得益于Proxy对象的使用,它提供了一种更清晰的方式来拦截和自定义对象的操作。在Vue3中,Proxy被用来替换Vue2中的Object.defineProperty方法,使得数据绑定更加高效和强大。Proxy对象允许开发者定义基本操作的自定义行为,如属性查找、赋值、枚举、函数调用等。它类似于一个拦截器,可以在访问对象属性时进行拦截,并可以自定义这些行为。

reactive API:Vue 3 中的 reactive 函数用于创建响应式对象。它接受一个普通对象作为参数,并返回该对象的响应式代理。在内部,reactive 使用了 ES6 的 Proxy 对象来监听对象的访问和修改,从而实现数据的响应式更新。

import { reactive } from 'vue';

const state = reactive({
  count: 0
});
  1. Proxy 对象reactive 函数内部使用了 Proxy 对象来实现数据的拦截和监听。当访问响应式对象的属性时,Proxy 会触发 getter 函数,从而建立依赖关系;当修改响应式对象的属性时,Proxy 会触发 setter 函数,从而通知相关依赖进行更新。

  2. 依赖追踪:当访问响应式对象的属性时,Vue 3 会通过 reactiveProxy 内部的机制进行依赖追踪。Vue 会记录当前组件对响应式对象属性的访问,并建立响应式依赖关系。

  3. 派发更新:当响应式对象的属性发生变化时,Proxy 会触发 setter 函数,并通知相关依赖进行更新。Vue 3 通过派发更新的方式,触发组件的重新渲染,从而实现了数据的双向绑定。

源码解析:

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}

reactive内部调用了createReactiveObject函数,并传入mutableHandlers作为Proxy的handler。

createReactiveObject()
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 reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

 getTargetType函数

function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

Object.isExtensible() 静态方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。 

toRawType 函数的作用是获取给定值的原始类型。这个函数通常用于内部实现,用于确定值的类型,以便在响应式系统中做出相应的处理。

TargetType: 

enum TargetType {
  INVALID = 0,
  COMMON = 1,
  COLLECTION = 2,
}
  • Common:普通对象,可以被转换为响应式对象。
  • Collection:集合对象,例如 MapSetWeakMap 等,需要特殊处理以确保其内部的每个属性也能够被响应式地跟踪。
  • Invalid:无效的对象,不应该被转换为响应式对象。
export enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  IS_SHALLOW = '__v_isShallow',
  RAW = '__v_raw',
}
  1. SKIP: 用于跳过对当前属性的依赖追踪。在某些情况下,例如遍历对象属性时,可能需要跳过对特定属性的依赖追踪,以避免不必要的性能开销。

  2. IS_REACTIVE: 表示当前对象是否已经是一个响应式对象。如果该标志位被设置,表示当前对象已经被转换为了响应式对象。

  3. IS_READONLY: 表示当前对象是否是只读的。当一个响应式对象被标记为只读时,任何尝试修改其属性的操作都会被阻止,以确保对象的不可变性。

  4. IS_SHALLOW: 表示当前对象是否是浅层响应式的。在 Vue 3 中,可以选择将对象转换为浅层响应式对象,即只对对象的顶层属性进行响应式转换,而不会递归地将其内部所有属性都转换为响应式。

  5. RAW: 表示当前对象是否是一个原始对象。原始对象是指普通的非响应式对象,它们没有被转换为响应式对象。

targetTypeMap函数:

function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}

toRawType 函数的作用是获取给定值的原始类型。这个函数通常用于内部实现,用于确定值的类型,以便在响应式系统中做出相应的处理。


export const objectToString = Object.prototype.toString
export const toTypeString = (value: unknown): string =>
  objectToString.call(value)

export const toRawType = (value: unknown): string => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1)
}

通过一系列的判断确定使用哪一个handler

    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,

  • baseHandlers 用于处理普通的对象,例如普通的 JavaScript 对象(Plain Object)和数组(Array)等。
  • collectionHandlers 则用于处理集合对象,包括 MapSetWeakMapWeakSet 等。

接下来我们看一下mutableHandlers方法做了什么事情

export const mutableHandlers: ProxyHandler<object> =
  /*#__PURE__*/ new MutableReactiveHandler()

MutableReactiveHandler类

这个类里面主要就是trigger方法 用于数据更新时触发相应的方法

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

  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    let oldValue = (target as any)[key]
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    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
  }
}

​

具体来说,hadKey逻辑如下:

  • 首先,使用 isArray 函数检查 target 是否为数组,同时使用 isIntegerKey 函数检查 key 是否为整数类型的键。
  • 如果 target 是数组且 key 是整数类型的键,并且 key 的值小于 target 的长度(即 target.length),则返回 true
  • 否则,使用 hasOwn 函数检查 target 是否具有自己的属性 key,如果是,则返回 true,否则返回 false

判断时set属性还是add属性。

MutableReactiveHandler继承与BaseReactiveHandler

这个类里面主要就是track方法 用于收集依赖

class BaseReactiveHandler implements ProxyHandler<Target> {
  constructor(
    protected readonly _isReadonly = false,
    protected readonly _isShallow = false,
  ) {}

  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      isShallow = this._isShallow
    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 reciever 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) {
      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 (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
  }
}

我们主要看trigger方法:

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>,
) {

    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }
/**首先,通过条件判断 key !== void 0 来确保键 key 不是未定义或未初始化的值。这是为了避免将未定义的键传递给 depsMap.get(key),从而避免出现错误。
如果键 key 是有效的,则调用 depsMap.get(key) 方法从 depsMap 中获取与该键相关联的依赖。
将获取到的依赖添加到 deps 数组中。deps 数组通常用于存储与某个特定属性或键相关联的所有依赖,以便在属性发生变化时可以通知到这些依赖进行更新。**/


    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }

  pauseScheduling()
  for (const dep of deps) {
    if (dep) {
      triggerEffects(
        dep,
        DirtyLevels.Dirty,
        __DEV__
          ? {
              target,
              type,
              key,
              newValue,
              oldValue,
              oldTarget,
            }
          : void 0,
      )
    }
  }
  resetScheduling()
}
  • ADD 操作类型下:

    • 如果目标对象 target 不是数组,则将与迭代键(ITERATE_KEY)相关联的依赖添加到 deps 数组中。
    • 如果目标对象 target 是 Map,则除了添加与迭代键相关联的依赖外,还会添加与 Map 迭代键(MAP_KEY_ITERATE_KEY)相关联的依赖。
    • 如果目标对象 target 是数组,并且添加的键 key 是整数类型的键,则将与数组长度('length')相关联的依赖添加到 deps 数组中,因为数组长度发生了变化。
  • DELETE 操作类型下:

    • 类似于 ADD 操作类型,如果目标对象 target 不是数组,则将与迭代键(ITERATE_KEY)相关联的依赖添加到 deps 数组中。
    • 如果目标对象 target 是 Map,则除了添加与迭代键相关联的依赖外,还会添加与 Map 迭代键(MAP_KEY_ITERATE_KEY)相关联的依赖。
  • SET 操作类型下:

    • 如果目标对象 target 是 Map,则将与迭代键(ITERATE_KEY)相关联的依赖添加到 deps 数组中。

for (const dep of deps) {}这段代码主要用于循环遍历依赖数组 deps,并对每个依赖执行触发效果。具体来说,它的作用如下:

  • 使用 for...of 循环遍历 deps 数组中的每个依赖 dep
  • 对于每个非空的依赖 dep,调用 triggerEffects 函数触发效果。
  • triggerEffects 函数用于触发指定依赖的效果,将其标记为脏(dirty),以便稍后重新计算或更新。它接受三个参数:依赖对象 dep、脏状态的级别 DirtyLevels.Dirty,以及可选的其他参数对象(用于调试时传递额外的信息)。
  • 在开发环境下,会传递包含目标对象 target、操作类型 type、键 key、新值 newValue、旧值 oldValue 和旧目标对象 oldTarget 等信息的参数对象,以便进行调试和跟踪。

pauseScheduling: 这个方法用于暂停 Vue 3 内部的调度器,阻止组件的更新。一旦调用了 pauseScheduling,Vue 将停止调度任务的执行,直到你调用 resetScheduling 方法来恢复调度器的工作。

resetScheduling: 这个方法用于重新启动 Vue 3 内部的调度器,恢复组件更新的调度。当调用 resetScheduling 后,Vue 3 将继续按照正常的调度机制来更新组件。

这两个方法有兴趣的可以自行查看一下源码。

track方法:收集依赖 有兴趣可以自己研究一下。

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && 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 = createDep(() => depsMap!.delete(key))))
    }
    trackEffect(
      activeEffect,
      dep,
      __DEV__
        ? {
            target,
            type,
            key,
          }
        : void 0,
    )
  }
}

  • 48
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值