阅读 391

逐行阅读Vue3响应式系统核心源码(reactive和effect)

前言

自从很久之前(已经忘了多久了)Vue发布了alpha版本以来,网上已经涌现了一大批优秀的博客、文章来分析Vue3的全新的响应式系统,所以我在这个时候再写一篇分析关于Vue响应式系统的文章可以说是在炒冷饭的感觉了。不过我还是想把自己看源码时的想法记录分享出来供大家的批评指正(顺便在掘金混个脸熟,提升提升等级,😁😁😁)。闲话不多说,我们直接开始吧。

正文

简单说一下怎么调试吧

Vue3的源码已经为我们搭建好了非常完美的在vscode中的调试环境,我们从Vue3仓库中将源码clone下来(推荐先fork到自己的仓库中,再clone自己仓库下面的源码),然后使用vscode打开源码目录。vue3响应式系统中的源码在packages/reactivity目录下,因此这片文章中我们主要分析packages/reactivity下面的源码。

packages/reativity目录文件如下:src目录下存放着响应式系统真正的代码,__tests__对应着单元测试。

我们选择__test__目录下effect.spec.ts来调试响应式系统的源码。我们打开effect.spec.ts文件,然后在对象的地方搭上断点,打开vscode页面,点击调试按钮,就可以开始调试了(或者按f5直接开始调试)

正式阅读源码

我们就就用上一个图中的单元测试作为例子开始一步步的阅读Vue3.0当核心的reactiveeffect的源码。阅读之前请先保证您熟悉ES6的相关知识,尤其是Proxy相关的内容,不熟悉的朋友可以先阅读一下阮一峰老师的ES6入门教程

let dummy
// 创建响应式对象
const counter = reactive({ num: 0 })
const fn = () => {
  dummy = counter.num
}
effect(fn)

expect(dummy).toBe(0)
counter.num = 7
expect(dummy).toBe(7)
复制代码

reactive

单元测试中首先调用reactive函数创建一个响应式对象(响应式proxy,后文中响应式proxy响应式对象是同一个意思), reactive函数定义在src/reactive.ts文件中,reactive的定义实现如下,它接受一个object,然后返回一个响应式proxy。


export interface Target {
  [ReactiveFlags.SKIP]?: boolean // 是否跳过响应式
  [ReactiveFlags.IS_REACTIVE]?: boolean // 是否是响应式对象
  [ReactiveFlags.IS_READONLY]?: boolean // 是否是readonly对象
  [ReactiveFlags.RAW]?: any // 响应式对象的原始数据
}
...
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.
  // 如果当前target已经是一个只读proxy了,则直接返回
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 调用createReactiveObject创建响应式对象
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  )
}
复制代码

reactive首先判断target是不是一个只读的proxy,如果是则直接返回。

在这里我先说一点后面分析时会说到的内容, 调用reactive函数创建的的响应式proxy中会定义一个ReactiveFlags.IS_REACTIVE属性,该属性为true时表明该proxy是一个响应式proxy,而readonly返回的readonly proxy会定义一个ReactiveFlags.IS_READONLY属性来表明该proxy是一个只读proxy,所以reactive函数开始会先判断target是否是一个只读proxy,如果是,则直接返回,防止修改到只读proxy中的数据

 // 如果当前target已经是一个只读proxy了,则直接返回
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
复制代码

接着reactive调用createReactiveObject函数来创建响应式对象。

return createReactiveObject(
  target,
  false,
  mutableHandlers,
  mutableCollectionHandlers
)
复制代码

reactivecreateReactObject传递了四个参数,这四个参数代表意义我们可以从createReactiveObject的定义可以看出,target是我们传给reactive的参数,是个普通object或者是集合(Map,Set, WeekMap,WeekSet)。isReadonly在当我们使用readonly函数创建只读proxy时为true,表示创建一个只读的proxy,baseHandlerscollectionHandlers都是使用new Proxy时传递给Proxy构造函数的第二个参数。

/**
 * 
 * @param target 传入的object,或者集合(map、set之类)
 * @param isReadonly 是否是只读
 * @param baseHandlers target是普通object时 传给Proxy构造函数的第二个参数
 * @param collectionHandlers target是集合(map、set等)时 传给Proxy构造函数的第二个参数
 */
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
...
}
复制代码

我们可以先看一下reactive传递给createReactiveObjectbaseHandlerscollectionHandlers是怎样子的。代码在src/baseHandlers文件下

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
...
export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: createInstrumentationGetter(false, false)
}
复制代码

我们可以看到mutableHandlers定义了getset等一系列的拦截器,而集合类型(Map,Set等)对应的mutableCollectionHandlers只定义了get拦截器,为什么这么做呢,我们不妨来看一个例子。

const map = new Map()
map.set('name', 'tom')
复制代码

上面中set的过程调用可以看作这样子

const set = map.set
set.call(map, 'name', 'tom')
复制代码

可以看出,在使用set函数设置新的键值时,会先获取Mapset函数,然后再去调用set去设置键值对。在获取Mapset函数会触发Proxyget拦截器。因此,在为集合类型的添加响应式能力时,我们只需要拦截Proxy的get操作,这样在Proxy调用相对应的函数(get,set等函数)时,可以返回重写之后的、加入了响应式逻辑处理的函数, 即可做到对集合类型做响应式处理。 至于handlers中的get, set等一些列拦截器是如何定义的,我们先不去探究,等到掉用到对应的拦截器时在去看对应的代码,现在我们先看看createReactiveObject是如何定义的

createReactiveObject

createReactObject的定义如下

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  // 如何target不是一个object,则直接返回
  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
  // 如果target已经是一个响应式proxy,则直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 如果target已经有对应的响应式proxy了,则获取对应的proxy并返回
  const proxyMap = isReadonly ? readonlyMap : reactiveMap
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  // 判断target是否是可以被proxy的类型,如果不是则直接返回
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 创建响应式proxy
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  // 将target映射到proxy中,
  proxyMap.set(target, proxy)
  // 返回
  return proxy
}
复制代码

首先createReactObject会先判断target是否是一个对象,如果不是,则直接返回

if (!isObject(target)) {
  if (__DEV__) {
    console.warn(`value cannot be made reactive: ${String(target)}`)
  }
  return target
}
复制代码

然后在判断target是否是一个响应式对象,如果是则直接返回target。(这里的ReactiveFlags.RAWReactiveFlags.IS_REACTIVE属性是什么时候定义的在后面讲get拦截器时会讲到)

if (
  target[ReactiveFlags.RAW] &&
  !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
  return target
}
// 这样的情况
/*
const r1 = reactive({})
const r2 = reactive(r1)
r1 === r2 // true
*/
复制代码

接着,判断target是否有对象的响应式对象,也就是已经做过响应式处理了,如果是,获取其对应的响应式对象并返回

// 如果target已经有对应的响应式proxy了,则获取对应的proxy并返回
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
  return existingProxy
}
复制代码

readonlyMapreactiveMap是一个WeakMap,用来存储object和其对应的proxy之间的映射关系

export const reactiveMap = new WeakMap<Target, any>()
export const readonlyMap = new WeakMap<Target, any>()
复制代码

再接着判断target是否是可以被响应式处理的类型,如果不是,则直接返回。我们可以从getTargetType定义可以看出,只有特定的一些类型才可以做响应式处理

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
  }
}

function getTargetType(value: Target) {
	// value没有`ReactiveFlags.SKIP`这个属性且没有被‘冻结’的对象、数组、集合才能做响应式处理
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}
复制代码

做完上面的一系列操作之后,就可以创建响应式Proxy并返回了

// 创建响应式proxy
const proxy = new Proxy(
  target,
  targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 存储target和proxy的映射关系,
proxyMap.set(target, proxy)
// 返回
return proxy
复制代码

到这里,我们已经分析完了reactive创建响应式Proxy的流程了。接下来我们先来看看effect函数是如何实现的

const counter = reactive({ num: 0 })
// 分析到这了,接下来分析下面这几行代码
const fn = () => {
  dummy = counter.num
}
effect(fn)
复制代码

effect

effect函数的定义在src/effect.ts文件中。effect函数实现非常简单。如果fn是一个effect函数,先把fn.raw复制给fn,在调用createReactiveEffect创建reactiveEffect函数对象。如果optionslazy为false,则调用reactiveEffect函数。最后返回reactiveEffect函数对象。我们这个例子中options.lazyfalse,所以创建完成reactiveEffect函数对象后会直接调用创建好的reactiveEffect函数对象

export function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  // 如果fn是一个effect函数,则将fn.raw(原始函数)赋值给fn
  if (isEffect(fn)) {
    fn = fn.raw
  }
  // 创建effect对象
  const effect = createReactiveEffect(fn, options)
  // options中lazy为false,即effect不是懒加载的,则调用effect
  if (!options.lazy) {
    effect()
  }
  // 返回effect函数
  return effect
}
复制代码

reactiveEffect函数对用通过createReactiveEffect函数创建的,接下里我们看看createReactiveEffect是如何创建reactiveEffect函数的

createReactvieEffect

createReactEffect定义如下:

function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    if (!effectStack.includes(effect)) {
      cleanup(effect)
      try {
        enableTracking()
        effectStack.push(effect)
        activeEffect = effect
        return fn()
      } finally {
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}
复制代码

createReactiveEffect函数也非常简单,定义一个reactiveEffect函数,并为reactiveEffect设置一系列属性。最后返回reactiveEffect函数, reactiveEffect中的deps属性我们需要特别注意一下,该属性是用来收集effect所依赖的target的,就是我们使用reactive时传递的参数target的。

前面提到了在我们的例子中,调用createReactiveEffect创建完reactiveEffect函数对象后会立即执行reactiveEffect函数,接下来我们就来看一看reactiveEffect函数是执行过程是怎样的

reactiveEffect

reactiveEffect函数定义如下

const effect = function reactiveEffect(): unknown {
    // 如果active为false
    if (!effect.active) {
      // 如果options不存在scheduler则调用fn
      return options.scheduler ? undefined : fn()
    }
    // 如果自己不再effectStack中
    if (!effectStack.includes(effect)) {
      // 清空自己所依赖的target,并重新收集
      cleanup(effect)
      try {
        // 允许收集依赖
        enableTracking()
        // 将自己加入当前effectStack
        effectStack.push(effect)
        // 将自己设置为activeEffect
        activeEffect = effect
        // 调用fn
        return fn()
      } finally {
        // 将自己出栈
        effectStack.pop()
        // 重新设置 是否允许收集依赖依赖
        resetTracking()
        // 重新设置是否activeEffect
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
复制代码

reactiveEffect函数首先判断自己是否是active状态,如果不是,则判断options是否存在scheduler,如果不存在,则调用我们传入的fn函数并返回。

接下来判断当前的reactiveEffect(也就是自己)是否在effectStack中,如果不存在,则进入if语句的代码块

首先,reactiveEffect会清空自己所依赖的target。

cleanup(effect)
// ....
function cleanup(effect: ReactiveEffect) {
  const { deps } = effect
  if (deps.length) {
    for (let i = 0; i < deps.length; i++) {
      deps[i].delete(effect)
    }
    deps.length = 0
  }
}
复制代码

这里为什么要清空依赖呢,我们来看下面的例子

const a1 = {name: 'a1'}
const a2 = {name: 'a1'}
const b = {name: 'b', a: a1}
const r = reactive(b)
const e = effect(() => {
	console.log(r.name + r.a.name)
})

r.a = a2
复制代码

从上面的例子来看,一开始e所依赖的targetba1,也就是edeps数组中会在ba1两个依赖,当执行了r.a = a2之后,e就不再依赖a1了,而是依赖a2,所以如果不清除原来的依赖,当再次收集依赖时,edeps就会有ba1a2三个依赖,这明显是不合逻辑的

接着是一段try...finally...代码,在try代码中reactiveEffect首先调用enableTrackingshouldTrack设置为true(只有shouldTracktrue时才允许手机依赖,然后将自己加入到effectStack中,将activeEffect设为自己(记住这一点很重要),最后调用fn,在finally代码块中将状态还原到之前的状态。

try {
  // 允许收集依赖
  enableTracking()
  // 将自己加入当前effectStack
  effectStack.push(effect)
  // 将自己设置为activeEffect
  activeEffect = effect
  // 调用fn
  return fn()
} finally {
  // 将自己出栈
  effectStack.pop()
  // 重新设置 是否允许收集依赖依赖
  resetTracking()
  // 重新设置是否activeEffect
  activeEffect = effectStack[effectStack.length - 1]
}

...
export function enableTracking() {
  trackStack.push(shouldTrack)
  shouldTrack = true
}

export function resetTracking() {
  const last = trackStack.pop()
  shouldTrack = last === undefined ? true : last
}
复制代码

try代码块中最后调用的fn函数就是我们传递给effect的参数,fn通过counter.num获取num的值,这时候会出发Proxyget拦截器,也就是前面提到的mutableHandlersget函数(千呼万唤始出来,终于走到了get拦截器),接下来我们来看看get拦截器做了哪些工作

const fn = () => {
  dummy = counter.num
}
effect(fn)
复制代码

get拦截器

get拦截器定义在src/baseHandlers中,它是通过createGetter函数定义的

const get = /*#__PURE__*/ createGetter()

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 如果key是ReactiveFlags.IS_REACTIVE,ReactiveFlags.IS_READONLY, ReactiveFlags.RAW,则直接返回对应的值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      key === ReactiveFlags.RAW &&
      receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
    ) {
      return target
    }

    // 判断target是否是一个数组
    const targetIsArray = isArray(target)
    // 如果taget是数组,且key是arrayInstrumentations中的key,则返回arrayInstrumentations[key]
    if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // 获取原始值
    const res = Reflect.get(target, key, receiver)

    // 如果key是一些特殊的key,则直接返回结果
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      return res
    }

    // 如果不是readonly,则开始收集依赖
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 如果是浅响应式则直接返回
    if (shallow) {
      return res
    }

    // 如果是ref
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      // 如果不是target不是数组,或者target是数组但key不是数字,则返回res.value,否则返回res
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 如果res是一个对象,则对res进行响应式处理
    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)
    }
    // 返回res
    return res
  }
}
复制代码

get首先判断key是不是ReactiveFlags.IS_REACTIVEReactiveFlags.IS_READONLYReactiveFlags.RAW中的一个,如果是则返回对应的值。

接着,如果target数组而且key存在于arrayInstrumentations中,则返回Reflect.get(arrayInstrumentations, key, receiver)arrayInstrumentations保存着重写之后的数组的的方法

// 重写数组的方法
;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    const arr = toRaw(this)
    for (let i = 0, l = this.length; i < l; i++) {
      track(arr, TrackOpTypes.GET, i + '')
    }
    // we run the method using the original args first (which may be reactive)
    const res = method.apply(arr, args)
    if (res === -1 || res === false) {
      // if that didn't work, run it again using raw values.
      return method.apply(arr, args.map(toRaw))
    } else {
      return res
    }
  }
})
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
  const method = Array.prototype[key] as any
  arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
    pauseTracking()
    const res = method.apply(this, args)
    resetTracking()
    return res
  }
})
复制代码

然后使用Reflect(反射)获取target.key的值、调用track开始收集依赖,对res除了一系列处理之后并返回

    // 获取原始值
    const res = Reflect.get(target, key, receiver)

    // 如果key是一些特殊的key,则直接返回结果
    if (
      isSymbol(key)
        ? builtInSymbols.has(key as symbol)
        : key === `__proto__` || key === `__v_isRef`
    ) {
      return res
    }

    // 如果不是readonly,则开始收集依赖,readonly Proxy的属性是不可更改,自然就不需要收集依赖了
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 如果是浅响应式则直接返回
    if (shallow) {
      return res
    }

    // 如果是ref
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      // 如果不是target不是数组,或者target是数组但key不是数字,则返回res.value,否则返回res
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 如果res是一个对象,则对res进行响应式处理
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }
    // 返回res
    return res
复制代码

好了,到现在终于调用track函数开始收集依赖了,我们就看看track如何手机依赖的

track

track函数定义在src/effect.ts中。track的逻辑比较容易理解,在这里我就不多说了。需要注意的是,这里的depMap和dep中存储的是前面我们说到的reactiveEffect函数

export function track(target: object, type: TrackOpTypes, key: unknown) {
  // 如果shouldTrack为false,或者activeEffect为undefined,则直接返回
  if (!shouldTrack || activeEffect === undefined) {
    return
  }

  // 过去target对应despMap
  let depsMap = targetMap.get(target)
  // 如果不存在depsMap则创建
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // 获取target的key对应的dep Set
  let dep = depsMap.get(key)
  // 如果不存在则创建
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  // 如果dep没有activeEffect,即当前的effect没有被收集到target的key的依赖中,则添加当前effect到dep中
  if (!dep.has(activeEffect)) {
    // 收集effect
    dep.add(activeEffect)
    // 将dep添加到当前effect的deps中,在cleanup函数中可以直接获取到该dep
    activeEffect.deps.push(dep)
    // 开发环境下设置
    if (__DEV__ && activeEffect.options.onTrack) {
      activeEffect.options.onTrack({
        effect: activeEffect,
        target,
        type,
        key
      })
    }
  }
}
复制代码

track函数执行完成后,就完成了对应的依赖收集,接下来就是触发依赖。当代码执行到count.num = 7时,会出发Proxyset拦截器(也就是前面提到的mutableHandlersset函数),在set拦截器中触发依赖,接下来我们就来看看set做了哪些工作

set函数

set函数定义是在src/baseHandlers文件中,通过createSetter函数来创建

const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 获取原来的值
    const oldValue = (target as any)[key]
    if (!shallow) {
      // 获取value的原始值,value可能是一个reactive proxy
      value = toRaw(value)
      // 如果target不是数组,切oldValue不是Ref,且value不是Ref,则将value赋值给oldValue.value
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    // 判断key是否存在target中
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 通过Reflect设置target的key属性的值为trigget
    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)) {
      // 如果key不再target中,触发add操作
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 如果key存在target中且value不等于oldValue,则触发set操作
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}
复制代码

set函数前面就是通过一系列操作设置新的值(如何设置的大家可以看注释就好了),在最后的调用trigger函数来触发相对应的依赖,接下来我们就来看看trigger的具体操作

trigger

trigger函数定义在src/effect.ts文件中。trigger的职责也很简单,就是找到需要触发的effect(reactiveEffect),然后调用。

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  // 获取target的depsMap
  const depsMap = targetMap.get(target)
  // 如果不存在depsMap,则返回
  if (!depsMap) {
    // never been tracked
    return
  }

  // 定义effects 存储需要粗发的依赖
  const effects = new Set<ReactiveEffect>()
  // 定义add函数来将effect添加到effects
  const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
    if (effectsToAdd) {
      effectsToAdd.forEach(effect => {
        if (effect !== activeEffect || effect.options.allowRecurse) {
        // 如果effect不等于当前的effect,添加到effects
          effects.add(effect)
        }
      })
    }
  }

  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 如果是clear操作,则将depsMap所有的effect添加到effects
    depsMap.forEach(add)
  } else if (key === 'length' && isArray(target)) {
    // 如果target是数组且length改变了,
    depsMap.forEach((dep, key) => {
      // 如果key是length或者key >= newValue
      /**
       * key >= newValue等情况是怎么回事:
       * arr = [1, 2, 3]
       * 当arr.length = 1,newValue = 1
       * 而key有如下值 length, 0(下标), 1(下标), 2(下标),
       * 当arr.length = 1,arr[1] arr[2]的值就被删掉了,需要触发对应的依赖
       */
      
      if (key === 'length' || key >= (newValue as number)) {
        add(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    // 处理普通object的set,add和delete操作
    if (key !== void 0) {
      add(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    // 处理Array,Map,Set特殊类型的add,delete,set操作
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          add(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          add(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            add(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          add(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  // 调用reactiveEffect函数
  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }

  effects.forEach(run)
}
复制代码

结尾

到这里,其实Vue3响应式系统核心的整体流程就基本分析完成了。当然Vue3响应式系统的肯定不止这些内容,Vue3对数组,Map,Set等特殊的object做了特殊的响应式处理,同时也提供了computed,ref,readonly,shallowReactive等具有不同功能的响应式函数,虽然功能不同,但万变不离其宗,当我们理解了reactiveeffect的流程,去理解剩下的响应式函数就轻松了许多。

当然,我这边狗屁不同的文章也不一定能帮大家理解Vue3响应式的原理,要想真正的理解还需大家亲自去看Vue3的源码,亲自调试,才能真正的理解Vue3的响应式系统原理。