Vue3响应式原理 [Vue3源码系列_xiaolu]

前言

数据响应式是什么?

数据响应式是一种机制,能够侦测到数据的变化,然后响应这个变化到视图

而Vue3设计理念是声明式(Declarative)开发,最大的好处:数据驱动,不用关心dom,只用关心状态数据

Vue2和Vue3响应式的差别

Vue2使用Object.defineProperty实现响应式

Vue3使用Proxy实现响应式

来看看两者的区别:

Object.defineProperty:

  1. 通过对某个属性的gettersetter进行拦截,它只能监听这个属性值的变化
  2. 内部会有很多depobserver,会在闭包中占内存
  3. 数组的响应式操作还是通过改写Array.proptype的7个数组方法实现的响应式,通过数组下标不能响应式
  4. 在把嵌套层级较深的对象变成响应式的场景中,在组件初始化阶段把数据变成响应式时,遇到子属性仍然是对象的情况,会递归执行 Object.defineProperty 定义子对象的响应式

Proxy:

  1. 是对某个对象的劫持,这样它不仅仅可以监听对象某个属性值的变化,还可以监听对象属性的新增和删除
  2. 依赖关系的保存也有差距 WeakMap{target: {key: [cb1, cb2]}},利用了WeakMap的垃圾回收机制
  3. 要对一些数组方法进行重写
  4. 在把嵌套层级较深的对象变成响应式的场景中,只有在对象属性被访问的时候才会判断子属性的类型来决定要不要递归执行 reactive,这其实是一种延时定义子对象响应式的实现,在性能上会有一定的提升。
  5. 支持mapset这些新类型

这只是一小部分的优化,而在Vue3.2.0,迎来了一个很强大的响应式性能优化,这个之后会细说(使用位运算)

那么到了这里,我们正式开始分析Vue3的响应式模块

reactive

reactive方法 路径: core-main\packages\reactivity\src\reactive.ts

// From setup
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 如果是只读的,那就不需要进行依赖收集,直接返回
  if (isReadonly(target)) {
    return target
  }
  // To: createReactiveObject
  // 返回createReactiveObject方法调用结果
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}
复制代码

通常,我们都会使用reactiveref方法将数据进行响应式化,我们先从reactive开始分析

reactive方法做了什么?

  1. 判断target是否是只读的
  2. 如果target是只读的,原样返回,只读不用收集依赖
  3. 如果target不是只读的,就返回调用createReactiveObject方法的返回值

reactive方法只是做了一个判断: 判断传入的target是否是只读的

如果是只读的,就直接原样返回

如果不是只读的,调用createReactiveObject方法并返回其执行结果

createReactiveObject这个命名大概能猜出,这个方法的功能是创建一个响应式对象

那么来看此方法的实现

createReactiveObject

createReactiveObject方法 路径: core-main\packages\reactivity\src\reactive.ts

// From reactive:
// Reutrn To reactive: 返回经过proxy代理的对象,这个proxy代理对象内部的方法取决于handlers
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 如果不是对象,直接返回
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  // 如果目标已经是一个代理(响应式对象),返回它。
  // 例外:在reactive对象上调用 readonly() 不返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 如果对象在proxyMap上,直接返回,防止反复创建代理对象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  // 只能观察到值类型的白名单。
  // To: getTargetType:
  // Return From getTargetType: 获取target的类型
  // INVALID是除了Object Array Map Set WeakMap WeakSet之外的类型,也就是除了这类型之外的类型,返回
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 执行Proxy代理,对target对象进行代理
  // 会根据TargetType.COLLECTION判断使用哪个handlers进行代理
  // baseHandlers是一般值类型  collectionHandlers是map set这些类型
  // From: createReactiveObject
  // To: baseHandlers
  const proxy = new Proxy(
    target,
    // From: createReactiveObject
    // To: baseHandlers
    // Return From baseHandlers: 返回包含get、set、deleteProperty、ownKeys、has方法的对象
    // To: collectionHandlers
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 代理完之后,在proxyMap上收集代理对象,防止反复创建代理对象
  proxyMap.set(target, proxy)
  // 返回proxy代理对象 代理对象上的方法取决于handlers
  return proxy
}
复制代码

createReactiveObject方法做了什么?

  1. 执行一系列判断,如果target不是对象、如果target已经是一个代理、如果targetproxyMap上、如果targetINVALID类型、都直接返回target
  2. target对象进行代理,这次代理的第二个参数对象会根据TargetType.COLLECTION来决定使用哪个handlers对象进行代理
  3. 代理完之后,在proxyMap上收集代理对象,防止反复创建代理对象
  4. 返回proxy代理对象 此时代理对象上面已经有了一些方法,这些方法取决于使用了哪个hanlders对象进行代理

经过一系列判断后,创建并返回一个代理对象

这个代理对象上的方法来自于第二个参数,而第二个参数是什么要取决于类型:

  1. 如果是mapset这种类型,会使用collectionHandlers
  2. 如果是普通的类型,会使用baseHandlers

想要知道我们的代理对象上的方法有哪些,我们先从基础普通类型开始,先来看baseHandlers

baseHandlers 普通类型的handlers

mutableHandlers对象

mutableHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts

// From createReactiveObject:
// Return To createReactiveObject: 返回如下方法
export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
复制代码

调用reactive方法传过来的是mutablehandlers对象

可以看到,这个对象拥有五个方法: getsetdeletePropertyhasownKeys

所以代理对象上拥有这五个方法

get: 触发自读取操作

set: 触发自赋值操作

deleteProperty: 触发自 delete操作

has: 触发自 in 操作

ownKeys: 触发自 for in 操作

我们一个个来看这些方法

get -> createGetter

get来自createGetter方法 路径: core-main\packages\reactivity\src\baseHandlers.ts

// From get:
function createGetter(isReadonly = false, shallow = false) {
  // 返回get方法
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 如果key已经是reactive,返回!isReadonly
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
      // 如果key已经是readonly,返回isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
      // 如果key是shallow,返回shallow
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      // 用于给Set中屏蔽原型引起的更新(之后会细说)
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }
    // From createGetter:
    // To isArray:
    // Retrun From isArray: 返回Array.isArray 判断是否是数组类型
    // 判断target是否是数组
    const targetIsArray = isArray(target)
    // From: createGetter:
    // To hasOwn:
    // Return From hasOwn: 判断对象是否有指定的属性
    // 如果不是只读并且 target是数组类型 并且key存在于arrayInstrumentations上
    // 那么返回定义在arrayInstrumentations上的方法 也就是重写的数组方法
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 返回数组方法
      // From: createGetter:
      //todo To: arrayInstrumentations
      // 例如: 当执行arr.includes其实执行的是arrayInstrumentations.includes 这样就实现了重写数组方法
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // 使用Reflect是为了第三个参数的this
    const res = Reflect.get(target, key, receiver)
    // From crateGetter:
    // To isSymbol:
    // Return From isSymbol: 判断是否是Symbol类型
    // To isNonTrackableKeys:
    // Return From isNonTrackableKeys: 判断是否是非跟踪类型 __proto__,__v_isRef,__isVue
    // 不应该在副作用函数与Symbol这类值之间建立响应联系,
    // 如果key的类型是symbol,不需要收集依赖,返回
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }
    // 如果不是只读,才需要收集依赖,建立响应联系
    if (!isReadonly) {
      // From createGetter:
      // To: track
      // Return From track: 创建依赖集合,trackEffects收集依赖
      track(target, TrackOpTypes.GET, key)
    }
    // 如果是shallow浅响应式,返回经过一次依赖收集的res
    if (shallow) {
      return res
    }
    // From createGetter:
    // To: isRef
    // Return From isRef: 判断r上是否有__v_isRef属性,判断是否是Ref
    // 如果是Ref,脱Ref
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      // ref unwrapping - 不适用于 Array + integer key。
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      // 脱ref
      return shouldUnwrap ? res.value : res
    }
    // From createGetter:
    // To isObject:
    // Return From isObject: 判断是否是对象类型
    // 如果是对象,根据readonly来决定怎么递归,深只读,深reactive
    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.
      // 也将返回值转换为代理。 我们进行 isObject 检查
      // 这里是为了避免无效值警告。 还需要惰性访问只读
      // 并在此处进行反应以避免循环依赖。
      // From createGetter:
      //todo To: readonly
      return isReadonly ? readonly(res) : reactive(res)
    }
    // 最后返回res
    return res
  }
}
复制代码

createGetter方法做了什么?

  1. 判断key是否是reactivereadonlyshallow,并返回对应值
  2. 判断是否是数组,是数组的话,进行数组方法的重写 (todo)
  3. 使用Reflect.get获取res值,使用Reflect是为了第三个参数receiver(等同于this)
  4. 判断是否是symbol类型,如果是则返回
  5. 判断是否是只读,如果不是只读,调用track方法收集依赖 (todo)
  6. 判断shallow,如果是true,就返回,不是就继续
  7. 判断是否是Ref,如果是,脱Ref
  8. 判断是否是对象,如果是,根据深只读还是深响应式来递归readonlyreactive
  9. 最后返回res值

get内部除了一些判断之外,还会对数组方法进行重写,会调用track收集依赖,会根据是否是对象去递归readonlyreactive

收集依赖,耳熟能详的词啊,那就来看看这收集依赖是个什么样子吧

track

track方法 路径: core\packages\reactivity\src\effect.ts

// From createGetter:
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 判断shouldTrack和activeEffect
  if (shouldTrack && activeEffect) {
    // 这一整步是为了创建依赖集合,像这样的结构 targetMap: {target -> key -> dep -> effect}
    // targetMap是WeakMap类型,depsMap是Map类型,dep是Set类型
    // targetMap的key值是target,value值是depsMap
    // depsMap的key值是key,value值是dep
    // dep的key值是effect,也就是ReactiveEffect
    // 因此形成了相对应的依赖集合,这个会细说
    let depsMap = targetMap.get(target)
    // 不存在时,初始化依赖集合
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }
    let dep = depsMap.get(key)
    // 不存在时,初始化依赖集合
    if (!dep) {
      // From track:
      // To createDep:
      // Return From createDep: 创建一个set集合,并且set集合有两个关于响应式性能优化的属性w和n
      depsMap.set(key, (dep = createDep()))
    }
    // DEV忽略
    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined
    // 执行trackEffects,进行依赖的添加
    trackEffects(dep, eventInfo)
  }
}

复制代码

track方法做了什么?

  1. 判断shouldTrack和activeEffect
  2. 创建获取依赖集合
  3. 执行trackEffects方法

track内部最主要的一件事就是创建和获取依赖集合了

所谓的依赖集合,是一个三层结构的集合 target -> key -> dep -> effect,这样的依赖集合可以很清晰的知道依赖的对应关系

targetMap是WeakMap类型,depsMap是Map类型,dep是Set类型

  1. targetMap的key值是target,value值是depsMap
  2. depsMap的key值是key,value值是dep
  3. dep的key值是effect(可以叫做依赖也可以叫做副作用函数),也就是ReactiveEffect

创建获取依赖集合后,会执行trackEffects方法,来看其实现

trackEffects

trackEffects方法 路径: core\packages\reactivity\src\effect.ts

// From track:
// Return To track: 将activeEffect添加到dep中,并且将dep添加到activeEffect.deps中,deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够知道是否需要清理
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // 将shouldTrack置为false
  let shouldTrack = false
  //todo: 涉及响应式性能优化,后面一起来看,先不管,看else
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    // 完全清理模式。
    // 如果Dep有activeEffect,shouldTrack为false,代表不用收集,如果没有,则需要收集
    shouldTrack = !dep.has(activeEffect!)
  }
  // 判断shouldTrack
  if (shouldTrack) {
    // 如果shouldTrack为true,则将activeEffect添加到dep中
    dep.add(activeEffect!)
    // 并且将dep添加到activeEffect.deps中, deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够知道是否需要清理
    activeEffect!.deps.push(dep)
    // DEV忽略
        if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        Object.assign(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo
        )
      )
    }
  }
}
复制代码

trackEffects方法做了什么?

  1. 响应式性能优化相关(todo)
  2. 如果shouldTrack是true,将activeEffect添加到Dep中,这一步就叫做收集依赖
  3. dep添加到activeEffect.deps中,deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够拿到依赖集合进行清理(todo)

可以看到,trackEffects才是真正的将依赖收集起来的方法!

所以track会创建依赖集合确定对应关系,trackEffects将依赖添加到dep中,完成依赖收集

数组方法的重写

知道了track方法会收集依赖之后,我们回来看数组方法的重写

createGetter中,还留下了一个todo: 如果不是只读,并且target是数组类型,并且arrayInstrumentations对象上拥有对应的key,这种情况就是对数组的方法进行处理了

现在来看createArrayInstrumentations方法

createArrayInstrumentations方法 路径: core\packages\reactivity\src\baseHandlers.ts

// From createGetter:
// Return To createGetter: // 返回一个包含了重写的几个数组方法'push', 'pop', 'shift', 'unshift', 'splice','includes', 'indexOf', 'lastIndexOf'的对象
function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument identity-sensitive Array methods to account for possible reactive
  // values
  // includes indexOf lastIndexOf它们都根据给定的值返回查找结果
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    // this是代理数组
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      // 获取原始数组
      const arr = toRaw(this) as any
      // 循环代理数组
      for (let i = 0, l = this.length; i < l; i++) {
        // 对原始数组的每个值进行track依赖收集
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      // 执行includes indexOf lastIndexOf方法,将返回值赋值给res
      const res = arr[key](...args)
      // 如果返回值是-1或false,代表没找到
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        // 如果不起作用,返回用原始值调用的结果
        return arr[key](...args.map(toRaw))
      } else {
        // 如果找到了,返回方法的返回结果res
        return res
      }
    }
  })
  // instrument length-altering mutation methods to avoid length being tracked
  // which leads to infinite loops in some cases (#2137)
  // 会隐式修改数组长度的方法 当调用push时,会读取数组的length属性值,也会设置数组的length属性值,会导致两个独立的副作用函数互相影响,会导致栈溢出
  // 所以只要屏蔽这些方法对length属性值的读取,就可以了
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    // this是代理数组
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      // From: createArrayInstrumentations
      // To pauseTracking:
      // Return From pauseTracking: 停止依赖收集
      pauseTracking()
      // toRaw获取原始数组,并执行相应方法 push pop shift unshift splice,将结果赋值给res
      const res = (toRaw(this) as any)[key].apply(this, args)
      // From: createArrayInstrumentations
      // To resetTracking:
      // Return From resetTracking: 重启依赖收集
      resetTracking()
      // 将执行结果res返回
      return res
    }
  })
  // 返回instrumentations对象,包含了重写的几个数组方法'push', 'pop', 'shift', 'unshift', 'splice','includes', 'indexOf', 'lastIndexOf'
  return instrumentations
}
复制代码

重写方法做了什么?

  1. includesindexOflastIndexOf它们都根据给定的值返回查找结果的方法进行重写,因为会在代理数组中查找,但是用户的本意肯定是在原始数组中查找,所以在代理数组中找不到,就继续去原始数组中招
  2. 对会隐式修改数组长度的方法pushpopshiftunshiftsplice进行重写,修改数组长度的方法会读取数组的length属性和改变数组的length属性,这两个变化对应的是两个副作用函数: 当读取数组的length属性的副作用函数执行时,改变数组的length属性的副作用函数也会执行,而其内部又会触发读取数组的length属性的副作用函数执行,因此会导致栈溢出(Maximun call stack size),所以要重写这几个方法。

这一步的注释在: 数组方法重写

set -> createSetter

此时get看完了,来看set

createSetter方法 路径: core\packages\reactivity\src\baseHandlers.ts

// From set:
// Return To Set: 返回set方法,内部会触发依赖,执行相关联的副作用函数
function createSetter(shallow = false) {
  // 返回set方法
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 获取旧值
    let oldValue = (target as any)[key]
    // From createSetter:
    // To isReadonly:
    // Return From isReadonly: 判断是否是只读类型 根据据value上是否有ReactiveFlags.IS_READONLY属性判断
    // To isRef:
    // Return From isRef: 判断是否是Ref类型 根据value上是否有__v_isRef属性判断
    // 如果是只读并且Ref并且新值不是Ref,返回false
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    // 如果shallow是false(浅响应式),并且新值不是只读
    if (!shallow && !isReadonly(value)) {
      // From createSetter:
      // To isShallow:
      // Return From isShallow: 判断是否是浅响应式 根据value上是否有ReactiveFlags.IS_SHALLOW属性判断
      // 如果新值也不是浅响应式
      if (!isShallow(value)) {
        // From createSetter:
        // To: toRaw
        // Return From toRaw: 返回原始的代理对象
        value = toRaw(value)
        oldValue = toRaw(oldValue)
      }
      // 如果target不是数组,并且老值是Ref并且新值不是Ref
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        // 直接把新值赋值给老值的value
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
      // 在浅模式下,无论是否反应,对象都按原样设置
    }

    const hadKey =
      // From createSetter:
      // To isIntegerKey:
      // Return From isIntegerKey: 判断是否是数字索引
      // 如果target是数组,并且key是数字(索引)
      isArray(target) && isIntegerKey(key)
        ? // 如果索引小于数组长度,代表没有新增,就是SET类型,如果不小于数组长度,代表新增了,就是ADD类型
          Number(key) < target.length
        : // 如果拥有对应的key,是SET类型,如果没有对应的key,代表要新增,就是ADD类型
          hasOwn(target, key)
    // 使用Reflect.set方法,receiver为了this
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // target === toRaw(receiver)就说明receiver就是target的代理对象,此目的是为了屏蔽由原型引起的更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // From createSetter:
        // To: trigger
        // Return From trigger: 将相对应的副作用函数(effect)推入到deps数组中,然后triggerEffects去遍历执行副作用函数
        // 如果hadkey为false,那么Trigger类型为ADD
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // From createSetter:
        // To hasChanged:
        // Return From hasChanged: 比较新值和旧值是否发生了变化,包含对 NaN的判断。
        // 如果新旧值发生了变化,那么以SET操作执行trigger,以key做关联
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    // 返回Reflect.set方法的返回值
    return result
  }
}
复制代码

createSetter方法做了什么?

  1. 判断旧值是否是只读,并且旧值是Ref并且新值不是Ref,返回false
  2. 判断shallow,并且判断新值不是只读
  3. 如果shallow是false并且新值不是只读,如果新值不是shallow,新旧值都toRaw
  4. 如果shallow是false并且新值不是只读,如果target不是数组,并且老值是Ref并且新值不是Ref,直接把新值赋值给老值的value
  5. 如果target是数组,并且key是数字(索引),如果索引小于数组长度,代表没有新增,就是SET类型,如果不小于数组长度,代表新增了,就是ADD类型
  6. 如果target不是数组,并且key不是数字,如果拥有对应的key,是SET类型,如果没有对应的key,代表要新增,就是ADD类型
  7. 执行Reflect.set方法
  8. 判断target是否等于toRaw(receiver),target === toRaw(receiver)就说明receiver就是target的代理对象,此目的是为了屏蔽由原型引起的更新
  9. 判断hadkey,如果hadkey为false,那么Trigger类型为ADD
  10. 如果新旧值发生了变化,就以SET操作执行trigger,以key做关联
  11. 返回Reflect.set方法的返回值

set内部会触发trigger触发依赖

trigger

trigger方法 路径: core\packages\reactivity\src\effect.ts

// From createSetter:
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  // 获取depsMap
  const depsMap = targetMap.get(target)
  // 如果depsMap不存在
  if (!depsMap) {
    // never been tracked
    // 从未被追踪,返回
    return
  }
  // 创建deps数组
  let deps: (Dep | undefined)[] = []
  // 如果type为CLEAR
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 集合被清空
    // 触发所有的依赖
    // 展开target里的所有值,并values执行get,触发所有依赖
    deps = [...depsMap.values()]
    // 如果target是数组并且修改了数组的length属性
  } else if (key === 'length' && isArray(target)) {
    // 遍历副作用函数,找出需要的副作用函数
    depsMap.forEach((dep, key) => {
      // 只有索引值大于等于length时,才需要添加到deps数组中 例如 一个数组有5个元素,你通过arr.length = 3改变数组,此时数组需要更新,但是arr.length = 7,不需要更新 
      if (key === 'length' || key >= (newValue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
      // 当类型为ADD时,新增
      case TriggerOpTypes.ADD:
        // 如果target不是数组
        if (!isArray(target)) {
          // ADD操作会使对象的键变多,会影响到for in循环的此处,因此取出与ITERATE_KEY关联的副作用函数,推入到deps数组中 (这里的ITERATE_KEY涉及ownKeys for in循环)
          deps.push(depsMap.get(ITERATE_KEY))
          // 如果target是map类型
          if (isMap(target)) {
            // ADD操作会使map的size属性变化,因此取出与MAP_KEY_ITERATE_KEY关联的副作用函数,推入到deps数组中
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // target是数组,并且key是正整数
          // ADD操作会使数组的length属性变化,因此取出与length属性相关的副作用函数,推入到deps数组中
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      // 当类型为DELETE时
      case TriggerOpTypes.DELETE:
        // 如果target不是数组
        if (!isArray(target)) {
          // DELETE操作会使对象的键变少,会影响到for in循环的次数, 因此又要取出取出与ITERATE_KEY关联的副作用函数,推入到deps数组中
          deps.push(depsMap.get(ITERATE_KEY))
          // 如果target是map类型
          if (isMap(target)) {
            // DELETE操作会使map的size属性变化,因此取出与MAP_KEY_ITERATE_KEY关联的副作用函数,推入到deps数组中
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      // 当类型为SET时 
      case TriggerOpTypes.SET:
        // 如果target是map类型
        if (isMap(target)) {
          // SET操作会使map的变化,因此取出与ITERATE_KEY关联的副作用函数,推入到deps数组中
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }
  // DEV忽略
  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined
  // 判断deps数组是否只有一个,也就是只有一个副作用函数
  if (deps.length === 1) {
    if (deps[0]) {
      // DEV忽略
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        // 执行triggerEffects
        triggerEffects(deps[0])
      }
    }
  } else {
    // 如果不止一个副作用函数
    const effects: ReactiveEffect[] = []
    // 遍历deps数组
    for (const dep of deps) {
      if (dep) {
        // 将dep推入到effects中
        effects.push(...dep)
      }
    }
    // DEV忽略
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      // 涉及到set规范 执行triggerEffects
      triggerEffects(createDep(effects))
    }
  }
}
复制代码

trigger方法做了什么?

  1. 通过依赖集合获取相对应的副作用函数
  2. 判断类型,如果为clear,清空并触发依赖
  3. 如果是数组类型并且修改了length属性,只有索引值大于等于length时,才需要添加到deps数组中
  4. 接着判断操作
  5. 如果是ADD操作: 如果不是数组类型(那就是对象),ADD操作会使对象的键变多,会影响到for-in循环的次数,因此取出与ITERATE_KEY(与for-in循环有关)关联的副作用函数,推入到deps数组中。如果是map类型,ADD操作会使mapsize属性变化,因此取出与MAP_KEY_ITERATE_KEY关联的副作用函数,推入到deps数组中。如果是数组类型,ADD操作会使数组的length属性变化,因此取出与length属性相关的副作用函数,推入到deps数组中
  6. 如果是DELETE操作: 如果不是数组类型,DELETE操作会使对象的键变少,会影响到for-in循环的次数, 因此又要取出取出与ITERATE_KEY关联的副作用函数,推入到deps数组中。如果是map类型,DELETE操作会使mapsize属性变化,因此取出与MAP_KEY_ITERATE_KEY关联的副作用函数,推入到deps数组中
  7. 当类型为SET操作: 如果是map类型,SET操作会使mapsize变化,因此取出与ITERATE_KEY关联的副作用函数,推入到deps数组中
  8. 接着根据deps中的副作用函数的多少进行对应的triggerEffects

所以trigger方法就是将相对应变化的操作关联的副作用函数添加到deps数组中,然后将deps数组交给triggerEffects方法

这一步的注释在trigger

triggerEffects

triggerEffects方法 路径: core\packages\reactivity\src\effect.ts

// From trigger:
// Return To createSetter: 循环所用的副作用函数,根据有无调度器,使用不同方式执行副作用函数
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  // 找到相关依赖,循环所有的副作用函数
  for (const effect of isArray(dep) ? dep : [...dep]) {
    // 如果trigger触发执行的副作用函数与现在正在执行的副作用函数相同,则不触发执行
    if (effect !== activeEffect || effect.allowRecurse) {
      // DEV忽略
      if (__DEV__ && effect.onTrigger) {
        effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
      }
      // 如果一个副作用函数存在调度器
      if (effect.scheduler) {
        // 则调用该调度器,并将副作用函数作为参数传递
        effect.scheduler()
      } else {
        // 否则直接执行副作用函数
        effect.run()
      }
    }
  }
}
复制代码

triggerEffects方法做了什么?

  1. 遍历deps数组
  2. 判断trigger触发的副作用函数是否与正在执行的函数相同,如果相同,则不执行(这是为了避免栈溢出, 例如obj.count++这种,会触发getset操作,会互相调用,因此要进行这一层判断)
  3. 判断副作用函数是否存在调度器
  4. 如果存在调度器,则调用该调度器effect.scheduler,并将副作用函数作为参数传递,否则effect.run直接执行副作用函数

现在知道triggerEffects会取出对应的副作用函数,并将其执行就可以了,effect.run先留着,之后来说

这一步的注释在triggerEffects

get和set总结

get会对那几个数组方法进行重写,会执行track创建获取依赖集合,会执行trackEffects进行响应式性能优化(todo)并进行依赖收集

set,会执行trigger经过一系列判断,将相对应变化的依赖推入到deps数组中,triggerEffects会对deps数组中的依赖的副作用函数进行执行(通过effect.run或scheduler todo)

这一步的注释在: 返回get、set

此时mutableHandlers的get和set方法已经看完,现在来看剩下的方法:hasownkeysdeleteProperty

has

has方法 路径: core\packages\reactivity\src\baseHandlers.ts

// From mutableHandlers:
// Return To mutableHandlers: 如果不是Symbol值,就以HAS类型执行track收集依赖,并返回has操作的结果(has操作来自 in操作符)
function has(target: object, key: string | symbol): boolean {
  // key是否在target上,如果在,返回true,否则返回false
  const result = Reflect.has(target, key)
  // From has:
  // To: builtInSymbols
  // Return From builtInSymbols: 13个内置的Symbol值
  // 如果key不是Symbol类型 或者 key不在builtInSymbols集合上
  if (!isSymbol(key) || !builtInSymbols.has(key)) {
    // 以HAS类型进行track依赖收集,关联为key
    track(target, TrackOpTypes.HAS, key)
  }
  // 返回has操作的结果
  return result
}
复制代码

has方法是in操作符触发的,做了什么?

  1. 判断是否是symbol类型或者是否是内置的symbol
  2. 如果不是,就以HAS操作进行track依赖收集,关联为key
  3. 返回has操作的结果

这一步的注释在: has

deleteProperty

deleteProperty方法 路径: core\packages\reactivity\src\baseHandlers.ts

// From mutableHandlers:
// Return To mutableHandlers: 如果target有相应的key值,并且删除成功,就以DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去,并返回deleteProperty的操作结果
function deleteProperty(target: object, key: string | symbol): boolean {
  // hasOwn target上是否有相应的key,有则ture,无则false
  const hadKey = hasOwn(target, key)
  // 获取key值赋值给oldValue
  const oldValue = (target as any)[key]
  // 执行Reflect.deleteProperty操作删除key属性,并将返回值返回给result
  const result = Reflect.deleteProperty(target, key)
  // 如果hadKey为true result为true,代表有相应的key并成功删除
  if (result && hadKey) {
    // 以DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  // 返回deleteProperty的操作结果
  return result
}
复制代码

deleteProperty由delete操作触发,做了什么?

  1. 判断target上是否有对应的key
  2. 执行Reflect.deleteProperty方法删除key
  3. 如果有对应的key并且删除成功
  4. DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去
  5. 返回deleteProperty的操作结果

这一步的注释在: deleteProperty

ownKeys

ownKeys方法 路径: core\packages\reactivity\src\baseHandlers.ts

// From mutableHandlers:
// Return To mutableHandlers: 以ITERATE类型执行track收集依赖,如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组是对象,则使用ITERATE_KEY建立关联
function ownKeys(target: object): (string | symbol)[] {
  // 以ITERATE类型执行track收集依赖
  // 如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组,则使用ITERATE_KEY建立关联
  // 对象时,删除和增加属性值都会影响for in循环,所以用ITERATE_KEY为key做关联
  // 但是数组不一样,数组添加新元素或者修改长度都会影响for in循环,而添加新元素和修改长度都是修改length属性,因此要用length属性为key建立关联
  track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
  // 返回ownkeys的操作结果
  return Reflect.ownKeys(target)
}
复制代码

ownKeys由for in触发,做了什么?

  1. ITERATE类型执行track收集依赖,如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组是对象,则使用ITERATE_KEY建立关联
  2. 返回ownkeys的操作结果

这一步的注释在: ownKeys

mutableHandlers总结

此时五个方法都看完了,总结如下:

get(触发自读取操作): 触发track创建获取依赖集合,与副作用函数建立联系,能清楚的知道副作用函数的对应关系,然后通过trackEffects将其添加到deps中,进行依赖收集

set(触发自赋值操作): 触发trigger获取对应的副作用函数,根据类型判断是什么操作,添加相关的副作用函数到deps中,然后通过triggerEffects触发这deps中的副作用函数执行(通过effect.run或effect.scheduler)

has(触发自in操作): 如果不是Symbol值,就以HAS类型执行track收集依赖,并返回has操作的结果

deleteProperty(触发自delete操作): 如果target有相应的key值,并且删除成功,就以DELETE类型执行trigger触发依赖,key为关联,并将删除的老值传过去,并返回deleteProperty操作的结果

ownKeys(触发自for-in操作): 以ITERATE类型执行track收集依赖,如果操作目标是数组,则使用length属性作为key建立关联,如果不是数组是对象,则使用ITERATE_KEY建立关联

接下来我们回到effect.run的执行

此时是triggerEffects触发依赖通过effect.run执行相对应的副作用函数这一步

effect.run

effect.run方法 路径: core\packages\reactivity\src\effect.ts

  // From triggerEffects:
  run() {
    // 如果当前不是活跃的
    if (!this.active) {
      // 返回fn fn是effect函数传入的参数,通常是更新函数也就是副作用函数
      return this.fn()
    }
    //todo parent的操作涉及effectScrop
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      // 取得effect
      this.parent = activeEffect
      // 将this赋给activeEffect
      activeEffect = this
      // 将shouldTrack置为true
      shouldTrack = true
      //todo 涉及响应式性能优化
      trackOpBit = 1 << ++effectTrackDepth
      // 响应式性能优化的地方,先看else
      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        // 执行cleanupEffect
        cleanupEffect(this)
      }
      // 返回fn fn是effect函数传入的参数,通常是更新函数也就是副作用函数
      return this.fn()
    } finally {
      // 响应式性能优化
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth
      // 涉及effectScope
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined
    }
  }
复制代码

run方法做了什么?

  1. 判断active,如果是false,返回fn
  2. 对effectScope的处理
  3. 对响应式性能的优化 (todo)
  4. cleanupEffects 清除依赖
  5. 返回fn(这个fn就是前面说的相对应的副作用函数哦)执行结果

这一步的注释在: effect.run

可以看到在执行fn副作用函数之前,先执行了cleanupEffects方法清除依赖,但为什么要清除依赖呢?

举个例子:

例子来自《Vuejs设计与实现》 p50

effect(function effectFn() {
    document.body.innerText = obj.ok ? obj.text : 'not'
})
复制代码

当obj.ok的值为true时,会收集obj.ok和obj.text的依赖,当改变obj.ok为false后,我们期望的是,去改变obj.text值时,obj.text收集的依赖的副作用函数不会去执行。

但是如果没有清除依赖这一步的话,当我们将obj.ok设为false后,对obj.text的依赖还存在,那么当我们修改obj.text的值时,obj.text依赖的副作用函数还是会执行,这不是我们所期望的

因此每次副作用函数执行前,都会执行一次清除依赖。而在副作用函数执行时,会重新建立依赖

那么来看cleanupEffects的实现

cleanupEffects

cleanupEffects方法 路径:

// From effect.run:
// Return To run: 清空deps中所有依赖
function cleanupEffect(effect: ReactiveEffect) {
  // 解构出deps数组,trackEffects中activeEffect!.deps.push(dep)这一步就是为了这里
  const { deps } = effect
  // 如果deps不为空
  if (deps.length) {
    // 遍历deps
    for (let i = 0; i < deps.length; i++) {
      // 将每一个dep中的effect依赖(副作用函数)移除
      deps[i].delete(effect)
    }
    // 长度置为0
    deps.length = 0
  }
}
复制代码

cleanupEffects方法做了什么?

  1. 解构deps数组,trackEffects中activeEffect!.deps.push(dep)这一步就是为了这里
  2. 遍历deps去删除依赖(副作用函数)
  3. deps数组也重置为0

这一步就是清空deps中所有的依赖

这一步的注释在: cleanupEffect

知道了这个清除依赖之后,是时候来看看响应式优化相关的处理了,这个优化就是对清除依赖进行的优化

我们来看看之前留下的响应式优化相关吧

响应式优化(位运算 针对清除依赖)

在Vue3.2.0之前,每次副作用函数执行前,都会触发cleanupEffect清空deps中所有依赖,然后在副作用函数执行过程中重新收集依赖,此过程中涉及到大量的对set的增删操作

来看看是怎么进行优化的吧

dep的两个属性

createDep方法 路径: core\packages\reactivity\src\dep.ts

// From track:
// Return To track: 创建一个set集合,并且set集合有两个关于响应式性能优化的属性w和n
export const createDep = (effects?: ReactiveEffect[]): Dep => {
  //todo: 创建一个新的set集合dep,并添加两个属性w,n (涉及到响应式性能优化,会细说)
  const dep = new Set<ReactiveEffect>(effects) as Dep
  // w表示是否已经被收集
  dep.w = 0
  // n表示是否是新收集的
  dep.n = 0
  return dep
}
复制代码

回到createDep方法中,创建dep时,初始化了两个属性:其中 w 表示是否已经被收集(可以这么理解: 老依赖),n 表示是否新收集(新依赖)。

三个全局变量

三个全局变量 路径: core\packages\reactivity\src\effect.ts

// The number of effects currently being tracked recursively.
// 当前正在递归跟踪的effects数。
let effectTrackDepth = 0
// 递归嵌套层数
export let trackOpBit = 1

/**
 * The bitwise track markers support at most 30 levels of recursion.
 * This value is chosen to enable modern JS engines to use a SMI on all platforms.
 * When recursion depth is greater, fall back to using a full cleanup.
 * 按位跟踪标记最多支持 30 级递归。
 * 选择此值是为了使现代 JS 引擎能够在所有平台上使用 SMI。
 * 当递归深度更大时,回退到使用完全清理。
 */
const maxMarkerBits = 30
复制代码

effectTrackDepth: 当前正在递归跟踪的effects数

trackOpBit: 递归嵌套层数

maxMarkerBits: 最大嵌套层数

回到effect.run

现在继续回到effect.run中

effect.run 路径: core\packages\reactivity\src\effect.ts

 try {
      // 取得effect
      this.parent = activeEffect
      // 将this赋给activeEffect
      activeEffect = this
      // 将shouldTrack置为true
      shouldTrack = true
      // 使用左移运算符来表示递归的层数
      // effectTrackDepth初始为0 ++之后为1  1 << 1 = 2
      // 位运算如下: 0000001 左移一位   0000010 此时代表调用了一次effect 0000100 代表递归嵌套了两层
      // 使用位运算可以处理嵌套的effect
      trackOpBit = 1 << ++effectTrackDepth
      // 如果effectTrackDepth <= 30 调用initDepMarkers,否则降级到cleanupEffect
      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        // 执行cleanupEffect
        // From: run
        // To: cleanupEffect
        // Return From cleanupEffect: 清空依赖
        cleanupEffect(this)
      }
      // 返回fn fn是effect函数传入的参数,通常是更新函数也就是副作用函数
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }
      trackOpBit = 1 << --effectTrackDepth
复制代码

可以看到中间使用了左移位运算,那么这操作是为了什么?

使用trackOpBit来代表递归嵌套的层数,通过左移,可以知道现在嵌套了几层,这样就能处理嵌套的effect

effectTrackDepth小于等于30时,就调用initDepMarkers而不是cleanupEffects

initDepMarkers

initDepMarkers方法 路径: core\packages\reactivity\src\dep.ts

// From effect.run:
// Return To effect.run: 初始化deps的w属性,代表已经收集依赖
export const initDepMarkers = ({ deps }: ReactiveEffect) => {
  // 如果deps不为空
  if (deps.length) {
    // 遍历dpes
    for (let i = 0; i < deps.length; i++) {
      // 给每个dep的w属性与trackOpBit(记录递归嵌套的层数)进行 | 运算,代表相对应层次的已经收集依赖
      deps[i].w |= trackOpBit // set was tracked
    }
  }
}
复制代码

initDepMarkers方法做了什么?

  1. 如果deps不为空,遍历deps
  2. 给每个depw属性与trackOpBit(记录递归嵌套的层数)进行 | 运算,举例: w最开始的时候为0,tarckOpBit假设为000100,他们进行 | 运算,得到结果还是000100,这样就代表递归嵌套两次的依赖已经被收集,这样就能清晰的知道哪些依赖被收集了,也代表着这是老依赖

主要就是将现在deps的依赖全部用w属性表示,代表收集了的老依赖

执行完initDepMarkers,不要忘了还有最重要的fn副作用函数要执行

fn执行,会执行到trackEffects(这一步为什么会执行到这里,之后会在何时触发依赖收集里面说到,现在只要先知道会触发就行)

现在回到trackEffects之前留下的todo

回到trackEffects

trackEffects方法 路径: core\packages\reactivity\src\effect.ts

// From track:
// Return To track: 将activeEffect添加到dep中,并且将dep添加到activeEffect.deps中,deps就是一个与当前副作用函数存在联系的依赖集合,为了在清理时能够知道是否需要清理
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // 将shouldTrack置为false
  let shouldTrack = false
  // 如果effectTrackDepth <= 30 没有超过最大递归层数
  if (effectTrackDepth <= maxMarkerBits) {
    // From trackEffects:
    // To newTracked:
    // Retrun From newTracked: 判断是否是新的依赖
    // 如果不是新的依赖
    if (!newTracked(dep)) {
      // 设置为新的依赖
      dep.n |= trackOpBit // set newly tracked
      // From trackEffects:
      // To wasTracked:
      // Reutrn from wasTracked: 看dep.w和递归层数是否相同,如果相同,则说明dep.w已经被收集,返回true,如果不同,返回false
      // 如果已经收集了,shouldTrack为false,如果没有收集shuoldTrack为true
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    // 完全清理模式。
    // 如果Dep有activeEffect,shouldTrack为false,代表不用收集,如果没有,则需要收集
    shouldTrack = !dep.has(activeEffect!)
  }
}
复制代码

trackEffects方法做了什么?

  1. shouldTrack置为false
  2. 如果effectTrackDepth <= 30 没有超过最大递归层数,判断dep是否是新的依赖
  3. 如果不是是新的依赖,dep.ntrackOpBit进行 | 运算,代表新设置的依赖
  4. 如果已经收集了,shouldTrack为false,如果没有收集shuoldTrack为true

使用dep.n属性表示新收集的依赖

effect.run finally

现在回到effct.run执行的finally这里

effct.run finally 路径: core\packages\reactivity\src\effect.ts

finally {
      // 如果effectTrackDepth <= 30 调用finalizeDepMarkers
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }
      // 递归层数恢复到上一级
      trackOpBit = 1 << --effectTrackDepth
复制代码

finally里面做了什么?

  1. 如果effectTrackDepth <= 30 调用finalizeDepMarkers
  2. 递归层数恢复到上一级

finalizeDepMarkers

finalizeDepMarkers方法 路径: core\packages\reactivity\src\dep.ts

// From effect.run:
// Return To effect.run: 遍历deps,删除曾经被收集过但不是新的依赖,将新的依赖添加到deps中,最后得到的就是删除不必要的旧依赖后的deps
export const finalizeDepMarkers = (effect: ReactiveEffect) => {
  // 解构出effect中的deps
  const { deps } = effect
  // 如果deps不为空
  if (deps.length) {
    // 索引
    let ptr = 0
    // 遍历deps
    for (let i = 0; i < deps.length; i++) {
      // 取得dep
      const dep = deps[i]
      // 曾经被收集过但不是新的依赖,需要删除
      if (wasTracked(dep) && !newTracked(dep)) {
        // 删除依赖
        dep.delete(effect)
      } else {
        // 如果是新收集的依赖,则放入deps中
        deps[ptr++] = dep
      }
      // clear bits
      // 清空状态
      dep.w &= ~trackOpBit
      dep.n &= ~trackOpBit
    }
    // deps数组长度等于删除不必要依赖之后的长度
    deps.length = ptr
  }
}
复制代码

finalizeDepMarkers方法做了什么?

  1. 解构出effect中的deps,如果deps不为空,遍历deps
  2. 对每个dep进行判断,判断是否是曾经被收集过但不是新的依赖
  3. 如果是,删除依赖
  4. 如果不是,则放入deps
  5. 清空状态
  6. deps数组长度等于删除不必要依赖之后的长度

其实就是将之前initDepMarkers中收集的w老依赖trackEffects中收集的n新依赖进行对比,如果老依赖在新依赖里面没有,就需要删除,如果有就不需要删除,最后得到删除了不需要的老依赖的依赖数组

cleanupEffect会删除deps中所有的依赖,而优化后只需要删除不需要的老依赖,减少了对set的增删,因此就得到了相对应的优化

这一步的注释在: 响应式优化

reactive总结

  1. 通过createReativeObject方法创建拥有getsethasownkeysdeleteProperty的代理对象
  2. getset来自createGettercreateSetter方法
  3. get会触发track创建获取依赖集合,track会触发trackEffects进行依赖收集
  4. set会触发trigger获取相对应的依赖集合,trigger会触发triggerEffects进行调用相对应的副作用函数,也就是effect.run
  5. effect.run内部会执行initDepMarkers,使用dep.w标记老依赖
  6. 执行fn也就是副作用函数,内部会执行getget会执trackEffects
  7. trackEffects中使用dep.n标记新的依赖
  8. 最后在finally中通过dep.w和dep.n的对比,删除曾经被收集过但不是新的依赖,将新的依赖添加到deps中,最后得到的就是删除不必要的旧依赖后的deps
  9. 递归层数恢复到上一级

这就是reactive的一些操作了,其实看起来挺简单的是吧

看完了reactive,那我们就接着把其他三个一起看看吧

之前我们看的是mutableHandlers

现在在baseHandlers中还剩下:

  1. shallowReactiveHandlers对象
  2. readonlyHandlers对象
  3. shallowReadonlyHandlers对象

这三个对象分别对应三个响应式方法:

shallowReactiveHandlers: shallowReactive

readonlyHandlers: readonly

shallowReadonlyHandlers: shallowReadonlyHandlers

shallowReactive

shallowReactiveHandlers对象

shallowReactiveHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts

// From createReactiveObject:
// To extend: 
// Return From extend: extend就是Object.assign方法
// Return To createReactiveObject: 返回如下方法
export const shallowReactiveHandlers = /*#__PURE__*/ extend(
  {},
  mutableHandlers,
  {
    get: shallowGet,
    set: shallowSet
  }
)
复制代码

extend就是Object.assign

因此shallowReactiveHandlers对象是这样的

const shallowReactiveHandlers = {
    get: shallowGet,
    set: shallowSet,
    deleteProperty,
    has,
    ownKeys
}
复制代码

可以看到deletePropertyhasownKeys方法和reactive的是一样的

get和set则是shallowGetshallowSet

shallowGet

shallowGet 路径: core\packages\reactivity\src\baseHandlers.ts

const shallowGet = /*#__PURE__*/ createGetter(false, true)
// 传入参数后 shallow为true
function createGetter(isReadonly = false, shallow = false) 
复制代码

可以看到createGetter此次的shallow为true

这里的createGetter代码就不放上来了,可以自己去看一下,当shallow为true时,只会执行一次最表面的track,不会去判断是否是对象从而递归

因此shallowGet只会收集一次表面的依赖

shallowSet

shallowSet 路径: core\packages\reactivity\src\baseHandlers.ts

const shallowSet = /*#__PURE__*/ createSetter(true)
// 传入参数后 shallow为true
function createSetter(shallow = false)
复制代码

可以看到createSetter此次的shallow为true

看看createSetter内部怎么处理的

    // 如果shallow是false,并且新值不是只读
    if (!shallow && !isReadonly(value)) {
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
      // 在shallow模式下,无论是否是reactive,对象都按原样设置
    }
复制代码

之后的操作都还是一样的,只是shallow模式下面,对象都按原样设置

readonly

readonlyHandlers对象

readonlyHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts

// From createReactiveObject:
// Return To createReactiveObject: 返回如下方法
export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet,
  set(target, key) {
    if (__DEV__) {
      console.warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      console.warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}
复制代码

可以看到readonlyHandlers只有三个方法: getsetdeleteProperty

不过setdeleteProperty都只是返回true,不会执行其他操作

因此来看get

readonlyGet

const readonlyGet = /*#__PURE__*/ createGetter(true)
// 传入参数后 isReadonly为true
function createGetter(isReadonly = false, shallow = false)
复制代码

isReadonly为true代入进createGetter

    // 如果是只读,不会进行数组方法的重写
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // 如果是只读,不会进行track收集依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    // 如果是对象,递归遍历执行readonly
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }
复制代码

可以看到isReadonly为true时,并不会收集依赖,不会进行数组方法的重写,如果是对象的话,还会递归执行readonly,都不会收集依赖

所以readonly不会收集依赖,只能读取值

shallowReadonly

shallowReadonlyHandlers对象

shallowReadonlyHandlers对象 路径: core-main\packages\reactivity\src\baseHandlers.ts

// From createReactiveObject:
// Return To createReactiveObject: 返回如下方法
export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
  {},
  readonlyHandlers,
  {
    get: shallowReadonlyGet
  }
)
复制代码

经过extends之后的shallowReadonlyHandlers对象

const shallowReadonlyHandlers = {
  get: shallowReadonlyGet,
  set(target, key) {
    if (__DEV__) {
      console.warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      console.warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}
复制代码

可以看到shallowReadonlyHandlers只有三个方法: getsetdeleteProperty

不过setdeleteProperty都只是返回true,不会执行其他操作

因此来看get

shallowReadonlyGet

const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true)
// 传入参数后 isReadonly为true shallow为true
function createGetter(isReadonly = false, shallow = false)
复制代码

isReadonly为true shallow为true代入进createGetter

    // 如果是只读,不会进行数组方法的重写
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    // 如果是只读,不会进行track收集依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }
    // 如果是shallow浅响应式,返回没有经过依赖收集的res
    if (shallow) {
      return res
    }
复制代码

可以看到,如果isReadonly为true、shallow为true,不会进行数组方法的重写,不会进行track收集依赖,并且在shallow这里返回后,不会进行对象的递归

所以shallowReadonly不会进行数组方法的重写,不会进行track收集依赖,并且不会进行对象的递归,形成浅只读

集合类型

集合类型里面涉及的方法较多,也不是很常用,所以我这里放了代码逐行注释,想了解的可以看一看,觉得繁琐的可以跳到下一段

现在分析完了baseHandlers,其实这只是一部分,让我们回到createReactiveObject方法中

const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
复制代码

可以看到,我们之前分析的是baseHandlers,现在如果是集合类型,就会走collectionHandlers

现在就来到collectionHandlers,而collectionHandlers其实是传进来的mutableCollectionHandlers

mutableCollectionHandlers

mutableCollectionHandlers 路径: core\packages\reactivity\src\collectionHandlers.ts

// From createReactiveObject
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  // get方法来自createInstrumentationGetter方法
  get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}
复制代码

可以看到mutableCollectionHandlers是一个包含get方法的对象,这个get方法来自createInstrumentationGetter方法

createInstrumentationGetter

createInstrumentationGetter方法 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableCollectionHandlers get:
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
  // 经过一系列判断获得instrumentations对象
  const instrumentations = shallow
    ? isReadonly
      ? shallowReadonlyInstrumentations
      : shallowInstrumentations
    : isReadonly
    ? readonlyInstrumentations
    : mutableInstrumentations
  // 返回一个函数,这个函数就是get
  return (
    target: CollectionTypes,
    key: string | symbol,
    receiver: CollectionTypes
  ) => {
    // 如果key是ReactiveFlags.IS_REACTIVE
    if (key === ReactiveFlags.IS_REACTIVE) {
      // 返回!isReadonly
      return !isReadonly
      // 如果key是ReactiveFlags.IS_READONLY
    } else if (key === ReactiveFlags.IS_READONLY) {
      // 返回isReadonly
      return isReadonly
      // 如果key是ReactiveFlags.RAW
    } else if (key === ReactiveFlags.RAW) {
      // 返回target
      return target
    }
    // 返回Reflect.get操作结果
    return Reflect.get(
      // 如果instrumentations对象是否有key 并且key在target上,返回instrumentations[key],否则返回target[key]
      hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver
    )
  }
}
复制代码

createInstrumentationGetter方法做了什么?

  1. 一系列判断
  2. 返回Reflect.get的操作结果,其中涉及判断 两处都涉及了instrumentations对象

instrumentations对象

instrumentations对象 路径: core\packages\reactivity\src\collectionHandlers.ts

// From createInstrumentationGetter:
const [
  mutableInstrumentations,
  readonlyInstrumentations,
  shallowInstrumentations,
  shallowReadonlyInstrumentations
] = /* #__PURE__*/ createInstrumentations()
复制代码

可以看到这几个对象都来自createInstrumentations方法

createInstrumentations

createInstrumentations方法 路径: core\packages\reactivity\src\collectionHandlers.ts

// From createInstrumentationGetter:
function createInstrumentations() {
  // mutableInstrumentations是键类型为string,值类型为Function的对象
  const mutableInstrumentations: Record<string, Function> = {
    // get方法
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    // size的getter
    get size() {
      return size(this as unknown as IterableCollections)
    },
    // has
    has,
    // add
    add,
    // set
    set,
    // delte
    delete: deleteEntry,
    // clear
    clear,
    // forEach
    forEach: createForEach(false, false)
  }
  // shallowInstrumentations对象
  const shallowInstrumentations: Record<string, Function> = {
    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)
  }
  // readonlyInstrumentations对象
  const readonlyInstrumentations: Record<string, Function> = {
    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)
  }
  // shallowReadonlyInstrumentations对象
  const shallowReadonlyInstrumentations: Record<string, Function> = {
    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)
  }
  // 通过createIterableMethod方法操作keys values entries Symbol.iterator迭代器方法
  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
  ]
}
复制代码

createInstrumentations方法做了什么?

  1. 创建了四个对象,对象内部有很多方法
  2. 通过createIterableMethod方法操作keysvaluesentriesSymbol.iterator迭代器方法
  3. 返回这四个对象

现在来看mutableInstrumentations内部的方法

get

get 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
function get(
  target: MapTypes,
  key: unknown,
  isReadonly = false,
  isShallow = false
) {
  // #1772: readonly(reactive(Map)) should return readonly + reactive version
  // of the value
  target = (target as any)[ReactiveFlags.RAW]
  // 获取原始target
  const rawTarget = toRaw(target)
  // 获取原始key
  const rawKey = toRaw(key)
  // 如果key不等于原始key
  if (key !== rawKey) {
    // 如果不是只读,以GET类型执行track,以key为关联
    !isReadonly && track(rawTarget, TrackOpTypes.GET, key)
  }
  // 以GET类型执行track,以key为关联,以rawKey为关联
  !isReadonly && track(rawTarget, TrackOpTypes.GET, rawKey)
  // From get:
  // To getProto:
  // Return From getProto: 返回对象原型
  // 从rawTarget获取原型,并从原型中解构出has方法
  const { has } = getProto(rawTarget)
  // From get:
  // To toShallow:
  // Return From toShallow: 啥也没做,返回value值
  // To toReadOnly:
  // Return From toReadOnly: 如果是对象,返回readonly(value),如果不是对象,返回value
  // To toReactive:
  // Return From toReactive: 如果是对象,返回reactive(value),如果不是对象,返回value
  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
  // 如果rawTraget上有key
  if (has.call(rawTarget, key)) {
    // 这里wrap是包裹一层响应式数据,避免数据污染
    // 返回包裹响应式的target.get(key)
    return wrap(target.get(key))
    // 如果rawTraget上有rawKey
  } else if (has.call(rawTarget, rawKey)) {
    // 这里wrap是包裹一层响应式数据,避免数据污染
    // 返回包裹响应式的target.get(rawKey)
    return wrap(target.get(rawKey))
    // 如果target不等于rawTarget 不等于原始target
  } else if (target !== rawTarget) {
    // #3602 readonly(reactive(Map))
    // ensure that the nested reactive `Map` can do tracking for itself
    // 确保嵌套的响应式 `Map` 可以自己进行跟踪
    target.get(key)
  }
}
复制代码

get做了什么?

  1. 如果不是只读,以GET类型执行track,以key为关联
  2. 并根据target上是否有keyrawKey返回经过wrap包裹的值

size

size 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To mutableInstrumentations: 如果不是只读,以TrackOpTypes.ITERATE类型执行track,以ITERATE_KEY为关联 因为改变size会影响for in循环,因此用ITERATE_KEY,并返回Reflect.get size的返回值
function size(target: IterableCollections, isReadonly = false) {
  // 获取原始target
  target = (target as any)[ReactiveFlags.RAW]
  // 如果不是只读 以TrackOpTypes.ITERATE类型执行track,以ITERATE_KEY为关联 因为改变size会影响for in循环,因此用ITERATE_KEY
  !isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
  // 返回Reflect.get size的返回值
  return Reflect.get(target, 'size', target)
}
复制代码

size做了什么?

  1. 如果不是只读 以TrackOpTypes.ITERATE类型执行track,以ITERATE_KEY为关联 因为改变size会影响for-in循环,因此用ITERATE_KEY
  2. 返回Reflect.get size的返回值

has

has 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To has: 根据key是否等于原始key,以GET类型执行track,以key或rawKey作为关联,判断key和rawKey是否相等,相等返回target.has(key)的结果,不相等返回target.has(key) || target.has(rawKey)的结果
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
  // 获取原始target
  const target = (this as any)[ReactiveFlags.RAW]
  // 原始traget
  const rawTarget = toRaw(target)
  // 原始key
  const rawKey = toRaw(key)
  // 如果key不等于原始key
  if (key !== rawKey) {
    // 如果不是只读,以GET类型执行track,以key为关联
    !isReadonly && track(rawTarget, TrackOpTypes.HAS, key)
  }
  // 以GET类型执行track,以key为关联,以rawKey为关联
  !isReadonly && track(rawTarget, TrackOpTypes.HAS, rawKey)
  // 判断是否相等,相等返回target.has(key)的结果,不相等返回target.has(key) || target.has(rawKey)的结果
  return key === rawKey
    ? target.has(key)
    : target.has(key) || target.has(rawKey)
}
复制代码

has做了什么?

  1. 根据key是否等于原始key,以GET类型执行track,以key为关联以key或rawKey作为关联
  2. 判断key和rawKey是否相等,相等返回target.has(key)的结果,不相等返回target.has(key) || target.has(rawKey)的结果

add

add 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To mutableInstrumentations: 如果value不在target上,执行target.add添加,以ADD类型执行trigger,以value为关联,将value值传过去,返回添加后的集合
function add(this: SetTypes, value: unknown) {
  // 原始value
  value = toRaw(value)
  // 原始集合
  const target = toRaw(this)
  // 获取target的原型对象
  const proto = getProto(target)
  // 调用原型对象的has方法,判断value是否在target中,在的话为true,不在为fasle
  const hadKey = proto.has.call(target, value)
  // 判断hadKey
  if (!hadKey) {
    // 如果hadKey为false,执行target.add方法
    target.add(value)
    // 以ADD类型执行trigger,以value为关联,将value值传过去
    trigger(target, TriggerOpTypes.ADD, value, value)
  }
  // 返回添加后的集合
  return this
}
复制代码

add做了什么?

  1. 调用原型对象的has方法,判断value是否在target
  2. 如果hadKey为false,执行target.add方法,以ADD类型执行trigger,以value为关联,将value值传过去
  3. 返回添加后的集合

set

set 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To set: 判断key是否在target上,如果不在,用原始key去判断,之后执行set方法,
// 在判断key是否在target上,如果在,并且新旧值发生改变,以SET类型执行trigger,以key为关联,将新旧值都传过去,
// 如果不在,以ADD类型执行trigger,以key为关联,将value值传过去
function set(this: MapTypes, key: unknown, value: unknown) {
  // 获取原始value
  value = toRaw(value)
  // 获取原始集合
  const target = toRaw(this)
  // getProto获取target的原型对象,并从中解构出has和get方法
  const { has, get } = getProto(target)
  // hadKey target是否有key,如果有为true,否则为false
  let hadKey = has.call(target, key)
  // 判断hadKey
  if (!hadKey) {
    // 如果hadKey为false,获取原始key
    key = toRaw(key)
    // 再用原始key去判断hadKey
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    // DEV忽略
    checkIdentityKeys(target, has, key)
  }
  // 获取oldValue
  const oldValue = get.call(target, key)
  // 调用set方法
  target.set(key, value)
  // 再判断hadKey
  if (!hadKey) {
    // 如果为false,以ADD类型执行trigger,以key为关联,将value值传过去
    trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {
    // 如果hadKey为true,并且新旧值发生改变,以SET类型执行trigger,以key为关联,将新旧值都传过去
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
  // 返回执行后的集合
  return this
}
复制代码

set做了什么?

  1. 判断key是否在target上,如果不在,用原始key再去判断
  2. get获取旧值,调用set
  3. 再判断hadkey,如果为false,以ADD类型执行trigger,以key为关联,将value值传过去
  4. 如果hadKey为true,并且新旧值发生改变,以SET类型执行trigger,以key为关联,将新旧值都传过去
  5. 返回执行后的集合

delete

delete 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To mutableInstrumentations: 判断key是否在target上,如果不在,用原始key去判断,之后执行delete方法
// 如果key在target上,以DELETE类型执行trigger,以key为关联,将undefined,oldValue传过去
// 返回delete执行的结果
function deleteEntry(this: CollectionTypes, key: unknown) {
  // 获取原始集合
  const target = toRaw(this)
  // 解构出原型对象上的has和get方法
  const { has, get } = getProto(target)
  // 判断key是否在target上,如果在,hadKey为true,如果不在hadKey为false
  let hadKey = has.call(target, key)
  // 判断hadKey
  if (!hadKey) {
    // 如果hadKey为false,获取原始key
    key = toRaw(key)
    // 用原始key再去判断hadKey
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    // DEV忽略
    checkIdentityKeys(target, has, key)
  }
  // 获取oldValue
  const oldValue = get ? get.call(target, key) : undefined
  // forward the operation before queueing reactions
  // 在queue reactions之前调用delete方法
  const result = target.delete(key)
  // 判断hadKey
  if (hadKey) {
    // 如果key在target上,以DELETE类型执行trigger,以key为关联,将undefined,oldValue传过去
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  // 返回delete执行的结果
  return result
}
复制代码

delete做了什么?

  1. 判断key是否在target上,如果不在,用原始key去判断
  2. 执行delete方法
  3. 如果key在target上,以DELETE类型执行trigger,以key为关联,将undefined,oldValue传过去
  4. 返回delete执行的结果

clear

clear 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To mutableInstrumentations: 执行clear,判断hadItems,如果为true,以CLEAR类型执行trigger,以undefined为关联,将undefined,oldTarget传过去,返回clear执行的结果
function clear(this: IterableCollections) {
  // 获取原始集合
  const target = toRaw(this)
  // 判断是否有值  size !== 0 为true  size === 0 为fasle
  const hadItems = target.size !== 0
  const oldTarget = __DEV__
    ? // DEV忽略
      isMap(target)
      ? new Map(target)
      : new Set(target)
    : undefined
  // forward the operation before queueing reactions
  // 在queue reactions之前调用clear方法
  const result = target.clear()
  // 判断hadItems
  if (hadItems) {
    // 如果hadItems为true,以CLEAR类型执行trigger,以undefined为关联,将undefined,oldTarget传过去
    trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
  }
  // 返回clear执行的结果
  return result
}
复制代码

clear做了什么?

  1. 执行clear
  2. 判断hadItems,如果为true,以CLEAR类型执行trigger,以undefined为关联,将undefined,oldTarget传过去
  3. 返回clear执行的结果

forEach

forEach 路径: core\packages\reactivity\src\collectionHandlers.ts

// From mutableInstrumentations:
// Return To mutableInstrumentations: 如果不是只读  以ITERATE类型执行track,以ITERATE_KEY为关联,返回forEach执行的结果
function createForEach(isReadonly: boolean, isShallow: boolean) {
  return function forEach(
    this: IterableCollections,
    callback: Function,
    thisArg?: unknown
  ) {
    // 获取集合
    const observed = this as any
    // 获取原始集合
    const target = observed[ReactiveFlags.RAW]
    // 获取原始target
    const rawTarget = toRaw(target)
    // wrap包裹响应式
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    // 如果不是只读  以ITERATE类型执行track,以ITERATE_KEY为关联
    !isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
    // 返回forEach执行的结果
    return target.forEach((value: unknown, key: unknown) => {
      // important: make sure the callback is
      // 1. invoked with the reactive map as `this` and 3rd arg
      // 2. the value received should be a corresponding reactive/readonly.
      // 重要:确保回调是
      // 1. 使用反应映射作为 `this` 和 3rd arg 调用
      // 2. 收到的值应该是相应的反应/只读。
      return callback.call(thisArg, wrap(value), wrap(key), observed)
    })
  }
}
复制代码

forEach做了什么?

  1. 如果不是只读 以ITERATE类型执行track,以ITERATE_KEY为关联
  2. 返回forEach执行的结果

createIterableMethod

createIterableMethod方法 路径: core\packages\reactivity\src\collectionHandlers.ts

// From createInstrumentations:
// Return To createInstrumentations: 如果不是只读,以ITERATE类型执行track,根据isKeyOnly决定以ITERATE_KEY或MAP_KEY_ITERATE_KEY为关联,返回一个包装的迭代器,它返回观察到的版本,实现可迭代协议
function createIterableMethod(
  method: string | symbol,
  isReadonly: boolean,
  isShallow: boolean
) {
  return function (
    this: IterableCollections,
    ...args: unknown[]
  ): Iterable & Iterator {
    // 获取原始对象
    const target = (this as any)[ReactiveFlags.RAW]
    const rawTarget = toRaw(target)
    // From createIterableMethod:
    // Return From createIterableMethod: 判断是否是map类型
    const targetIsMap = isMap(rawTarget)
    // 如果是entries或是Sybol.iterator方法并且是map类型 为true
    const isPair =
      method === 'entries' || (method === Symbol.iterator && targetIsMap)
    const isKeyOnly = method === 'keys' && targetIsMap
    // 获取原始迭代器方法
    const innerIterator = target[method](...args)
    // wrap包裹函数
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
    // 如果不是只读,以ITERATE类型执行track,根据isKeyOnly决定以ITERATE_KEY或MAP_KEY_ITERATE_KEY为关联
    !isReadonly &&
      track(
        rawTarget,
        TrackOpTypes.ITERATE,
        isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY
      )
    // return a wrapped iterator which returns observed versions of the
    // values emitted from the real iterator
    // 返回一个包装的迭代器,它返回观察到的版本
    // 从实际迭代器发出的值
    return {
      // iterator protocol
      next() {
        // 调用原始迭代器的next方法获取value和done
        const { value, done } = innerIterator.next()
        return done
        // 如果done存在返回value和done
          ? { value, done }
          : { // 如果done不存在,value根据isPair决定返回什么 使用wrap包裹
              value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
              done
            }
      },
      // iterable protocol
      // 实现可迭代协议
      [Symbol.iterator]() {
        return this
      }
    }
  }
}
复制代码

createIterableMethod做了什么?

  1. 如果不是只读,以ITERATE类型执行track,根据isKeyOnly决定以ITERATE_KEYMAP_KEY_ITERATE_KEY为关联
  2. 返回一个包装的迭代器,它返回观察到的版本,实现可迭代协议

集合类型总结

集合类型还有其他三个,和之前看的那些handlers对象其实差不多,就是在我们分析的这些方法上通过传入shallow,readonly参数改变方法的执行结果,我就不多去看了,大家可以自己结合上面的代码去分析分析

ref

现在来看ref

ref方法 路径: core\packages\reactivity\src\ref.ts

// ref
export function ref<T extends object>(
  value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value, false)
}
复制代码

ref方法做了什么?

  1. 返回createRef方法的调用返回值

可以看到ref其实内部是返回的createRef方法的调用返回值

createRef

createRef方法 路径: core\packages\reactivity\src\ref.ts

// From ref:
function createRef(rawValue: unknown, shallow: boolean) {
  // 判断是否是ref
  if (isRef(rawValue)) {
    // 如果是ref,直接返回
    return rawValue
  }
  // 返回一个RefImpl对象
  return new RefImpl(rawValue, shallow)
}

复制代码

createRef方法做了什么?

  1. 判断是否是ref,如果是,直接返回
  2. 如果不是,返回一个RefImpl对象

class RefImpl

class RefImpl 路径:core\packages\reactivity\src\ref.ts

// From createRef:
class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(value: T, public readonly __v_isShallow: boolean) {
    // 根据是否是shallow赋值
    // 如果不是shallow rawValue就是原始的value
    this._rawValue = __v_isShallow ? value : toRaw(value)
    // From RefImpl:
    // To: toReactive
    // Retrun From toReactive: 如果是对象,返回reactive(value),如果不是对象,返回value
    // 如果不是shaollw 如果value是对象,_value是toReactive(value),如果不是对象,_value是value
    this._value = __v_isShallow ? value : toReactive(value)
  }
  // 对value的getter
  get value() {
    // 执行trackRefValue
    trackRefValue(this)
    // 返回_value
    return this._value
  }
  // 对value的setter
  set value(newVal) {
    // 如果不是shallow newVal就是newVal的原始值
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    // 如果新、旧值发生改变
    if (hasChanged(newVal, this._rawValue)) {
      // 赋值
      this._rawValue = newVal
      // 如果不是shaollw 如果value是对象,_value是toReactive(newVal),如果不是对象,_value是newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      // 执行triggerRefValue
      triggerRefValue(this, newVal)
    }
  }
}
复制代码

class RefImpl做了什么?

  1. 构造函数内部初始化 _rawValue_value
  2. value属性的getter设置,getter会触发trackRefValue,并返回_value值
  3. value属性的setter设置,setter会根据新旧值是否改变,去改变值,并触发triggerRefValue

这一步的注释在: class RefImpl

触发get时会触发trackRefValue

触发set时会触发triggerRefValue

因此来看这两个方法

trackRefValue

trackRefValue方法 路径: core\packages\reactivity\src\ref.ts

// From RefImpl:
// Return To RefImpl: 调用trackEffects收集依赖
export function trackRefValue(ref: RefBase<any>) {
  // 判断shouldTrack和activeEffect
  if (shouldTrack && activeEffect) {
    // 如果shouldTrack为true,并且activeEffect不为null,使用toRaw获取原始ref
    ref = toRaw(ref)
    // DEV忽略
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      // 调用trackEffects方法收集依赖,这个依赖是收集到dep上的
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}
复制代码

trackRefValue方法做了什么?

  1. 如果shouldTrack为true,并且activeEffect不为null,使用toRaw获取原始ref
  2. 调用trackEffects收集依赖,这个依赖是收集到dep上的

trackRefValue也是调用了trackEffects进行依赖收集

triggerValue

triggerValue方法 路径: core\packages\reactivity\src\ref.ts

// From RefImpl:
// Return To RefImpl: 调用triggerEffects触发依赖,执行副作用函数
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  // toRaw获取原始ref
  ref = toRaw(ref)
  // 如果ref.dep存在
  if (ref.dep) {
    // DEV忽略
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      // 调用triggerEffects触发依赖,执行副作用函数
      triggerEffects(ref.dep)
    }
  }
}
复制代码

triggerValue方法做了什么?

  1. toRaw获取原始ref,判断ref.dep是否存在
  2. 存在的话,调用triggerEffects触发依赖,执行副作用函数

triggerValue也是调用triggerEffects触发依赖

ref总结

  1. createRef返回一个RefImpl对象
  2. class RefImpl对value属性设置getter、setter,还做了对对象的处理
  3. getter内部trackRefValue,通过trackEffects收集依赖
  4. setter内部triggerRefValue,通过triggerEffects触发依赖

computed

现在来看computed

computed方法 路径: core\packages\reactivity\src\computed.ts

// computed
export function computed<T>(
  getter: ComputedGetter<T>,
  debugOptions?: DebuggerOptions
): ComputedRef<T>
export function computed<T>(
  options: WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
  debugOptions?: DebuggerOptions,
  isSSR = false
) {
  // getter
  let getter: ComputedGetter<T>
  // setter
  let setter: ComputedSetter<T>
  // From computed:
  // To isFunction:
  // Return From isFunction: 判断是否是function类型
  // 判断getterOrOptions是否是函数,如果是函数就是get,如果不是就是getset
  const onlyGetter = isFunction(getterOrOptions)
  if (onlyGetter) {
    // 如果是函数,就将get函数赋值给getter
    getter = getterOrOptions
    // setter不用
    setter = __DEV__
      ? () => {
          console.warn('Write operation failed: computed value is readonly')
        }
      : NOOP
  } else {
    // 如果不是函数
    // 将对象的get赋给getter
    getter = getterOrOptions.get
    // 将对象的set赋给setter
    setter = getterOrOptions.set
  }
  // 创建一个新的computedRefImpl对象
  const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
  // DEV忽略
  if (__DEV__ && debugOptions && !isSSR) {
    cRef.effect.onTrack = debugOptions.onTrack
    cRef.effect.onTrigger = debugOptions.onTrigger
  }
  // 返回cRef对象
  return cRef as any
}

复制代码

computed方法做了什么?

  1. 判断getterOrOptions是否是函数
  2. 如果是函数,代表是get函数,赋值给getter
  3. 如果不是函数,代表是对象,将对象的get赋值给getter,将对象的set赋值给setter
  4. 创建一个新的computedRefImpl对象
  5. 返回cRef对象

因此来看class computedRefImpl

class computedRefImpl

class computedRefImpl 路径: core\packages\reactivity\src\computed.ts

// From computed:
export class ComputedRefImpl<T> {
  public dep?: Dep = undefined

  private _value!: T
  public readonly effect: ReactiveEffect<T>

  public readonly __v_isRef = true
  public readonly [ReactiveFlags.IS_READONLY]: boolean

  public _dirty = true
  public _cacheable: boolean

  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean,
    isSSR: boolean
  ) {
    // 通过new ReactiveEffect创建effect副作用函数,第二个参数是scheduler
    this.effect = new ReactiveEffect(getter, () => {
      // 当值发生变化时,判断dirty,使用调度器将dirty重置为true
      if (!this._dirty) {
        // 如果dirty为false,则设置dirty为true
        this._dirty = true
        // 手动调用triggerRefValue 避免嵌套的effect
        triggerRefValue(this)
      }
    })
    // 给effect设置computed
    this.effect.computed = this
    // 给effect设置active 如果是ssr则为false,如果不是就是true
    this.effect.active = this._cacheable = !isSSR
    this[ReactiveFlags.IS_READONLY] = isReadonly
  }
  // value的getter
  get value() {
    // the computed ref may get wrapped by other proxies e.g. readonly() #3376
    // 计算的 ref 可能会被其他代理包裹,例如 只读()
    // 通过toRaw获取原始的值
    const self = toRaw(this)
    // 手动调用trackRefValue,去收集依赖 避免嵌套的effect
    trackRefValue(self)
    // 判断dirty和_cacheable 只有脏了才计算值,
    if (self._dirty || !self._cacheable) {
      // 如果dirty为true或者_cacheable为false 代表脏了
      // 则设置dirty为false
      self._dirty = false
      // 执行self.effect.run()
      self._value = self.effect.run()!
    }
    // 返回value
    return self._value
  }
  // value的setter
  set value(newValue: T) {
    this._setter(newValue)
  }
}
复制代码

class computedRefImpl做了什么?

  1. 通过new ReactiveEffect创建effect副作用函数,第二个参数是scheduler,当发生改变时,会去执行调度器,也就是改变dirty的值,和手动调用triggerRefValue
  2. value属性设置getter,手动调用trackRefValue,通过dirty判断值是否改变,没改变就不需要计算,改变了就设置

computed总结

  1. 处理getter和setter
  2. class computedRefImpl通过dirty调度器完成了计算属性的缓存功能,当值发生改变时,执行调度器里面的dirty = true,之后执行get时就会重新计算,反之不会计算,就完成了缓存。
  3. 当发生改变时,会去执行调度器,也就是改变dirty的值,和手动调用triggerRefValue,对value属性设置getter,手动调用trackRefValue

一些关于响应式的问题

关于响应式模块的分析已经差不多结束了,现在消化一些问题吧!

何时进行首次收集依赖?

首先,要触发响应式,肯定要收集依赖,才能触发依赖执行副作用函数,那么首次的依赖收集是什么时候发生的呢?不可能等到我们手动调用才会去收集依赖吧

在我的Vue3组件初始化流程文章中说过调用update方法执行componentUpdateFn更新函数实现首次视图更新

componentUpdateFn更新函数内部会去执行template通过compile(编译器 之后文章会说) 生成的render方法,而render方法会读取属性,执行get操作,因此会进行收集依赖,所以首次依赖收集是在mountComponent中的setupRenderEffect方法中的update中发生的。

结合我的mini-vue来看看首次依赖收集的过程

依赖何时收集.png

当data和setup一起时,谁优先级高

前面提到过Vue2的兼容发生在applyOptions中,那么当datasetup中存在同名属性时,谁的优先级更高呢?

路径: packages\runtime-core\src\componentPublicInstance.ts

// 可以看到setup的优先级大于data
else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
    accessCache![key] = AccessTypes.SETUP
    return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
    accessCache![key] = AccessTypes.DATA
    return data[key]
}
复制代码

可以看到setup优先级大于data,因此setup和data一起使用时,同名优先级setup大于data

ref的value

ref在setup内要加上.value,而在模板中,只需要用ref就行了,这是在哪做了处理呢?

来自 handleSetupResult方法 路径: core\packages\runtime-core\src\component.ts

// From handleSetupResult:
// To proxyRefs:
// Return From proxyRefs: 如果是ref类型,返回一个新的ref代理对象,此代理对象上拥有get set方法,get方法返回ref.value,set方法赋值给ref.value,做一次代理,也就是模板内ref不用.value的原因
// 如果是对象,直接把值赋给instance.setupState,这就是用户的setup内部的状态
// 并对ref做一层代理
instance.setupState = proxyRefs(setupResult)
复制代码

可以看到在handleSetupResult方法中,对setup调用的结果执行了proxyRefs

proxyRefs方法 路径: core\packages\reactivity\src\ref.ts

// From handleSetupResult:
// Return To handleSetupResult: 如果是ref类型,返回一个新的ref代理对象,此代理对象上拥有get set方法,get方法返回ref.value,set方法赋值给ref.value,做一次代理,也就是模板内ref不用.value的原因
export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
  // From proxyRefs:
  // To isReactive:
  // Return From isReactive: 根据value上是否有ReactiveFlags.IS_REACTIVE属性判断是否是只读
  return isReactive(objectWithRefs)
    ? // 如果是reactive对象,原样返回
      objectWithRefs
    : // From proxyRefs:
      // To shallowUnwrapHandlers:
      // Return From shallowUnwrapHandlers: 返回一个拥有get set方法的对象,get方法返回ref.value,set方法赋值给ref.value,做一次代理
      new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

// From shallowUnwrapHandlers:
// Return To shallowUnwrapHandlers: 脱ref,也就是如果是ref类型,就返回ref.value,等于做了一层代理,也就是模板中的ref可以不用.value的原因
export function unref<T>(ref: T | Ref<T>): T {
  // 判断是否是ref类型  如果是的话返回ref.value 如果不是的话原样返回
  return isRef(ref) ? (ref.value as any) : ref
}
// From proxyRefs:
// Return To proxyRefs: 
const shallowUnwrapHandlers: ProxyHandler<any> = {
  // From shallowUnwrapHandlers:
  // To unref:
  // Return From unref: 对ref做一层代理,也就是模板中的ref可以不用.value的原因
  // get方法会返回ref.value  unref对ref做了一层代理,也就是模板中的ref可以不用.value的原因
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  // 
  set: (target, key, value, receiver) => {
    // 获取老值
    const oldValue = target[key]
    // 如果老值是ref类型 并且新值不是ref类型
    if (isRef(oldValue) && !isRef(value)) {
      // 将新值赋值给老值的value
      oldValue.value = value
      return true
    } else {
      // 返回Reflect.set的返回结果
      return Reflect.set(target, key, value, receiver)
    }
  }
}
复制代码

proxyRefs方法做了什么?

如果是ref类型的话,就返回一个拥有getset方法的代理对象,get方法通过unref进行脱ref处理,也就是返回ref.valueset方法赋值给ref.value

也就是说在proxyRefs中,完成了对ref类型的代理,因此在模板中使用的是经过代理后的ref了,因此不需要使用.value

总结

3.png

来看看目录结构,这些都不多已经逐行注释分析完了,除了deferredComputed,但这个不是我们需要分析的,尤大关于deferredComputed解释

注意:这不是 Vue API 的一部分,仅作为特定于 @vue/reactivity 的较低级别的 API

这一篇响应式原理文章,几乎是行行有注释,大家有不理解的地方可以参考我的github,也感谢大家支持!

猜你喜欢

转载自juejin.im/post/7086769876378861581