Vue3-响应式原理源码阅读记录【1】

79 阅读17分钟

vue3 与 vue2 主要差异之一无疑是响应式实现上的改变。本文主要阐述响应式原理的实现方式解析以及核心源码阅读的注释理解。

响应式原理源码路径 packages/reactivity/src/.*\.ts,本文主要涉及 reactive.tseffect.tseffectScope.tsdep.tsbaseHandlers.tscollectionHandlers.ts

前置准备

在阅读响应式原理之前,可能你需要先了解一下几个 js标准内置对象

下面只进行简单介绍,作为源码阅读的基础,具体API自行查看阅读。

Proxy

Proxy 是响应式原理实现的核心对象,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

vue2 通过 Object.defineProperty 修改对象的描述符( getter / setter),从而监听对象的取值(getter)行为进行收集依赖;监听赋值(setter)行为用以通知依赖更新

vue3 则使用了 Proxy 来创建响应式对象,通过Proxy捕捉对象的取值(getter)、赋值(setter)操作,从而实现依赖收集和依赖更新,仅将 getter / setter 用于 ref

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Proxy不是一个函数对象,因此它是不可构造的,不能使用 new 关键字进行调用~~new Reflect()~~。

Vue3 中主要用来对对象字段的常归操作进行代理。使用Reflect.get/Reflect.set 代替 . 号取值/赋值等

WeakMap

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象Object),而值可以是任意的。

由于WeakMap 持有的是每个键对象的“弱引用”,意味着在没有其他引用存在时垃圾回收能正确进行。WeakMap 的结构是特殊且有效的,其用于映射的 key _只有_在其没有被回收时才是有效的。

正由于这样的弱引用,WeakMap 的 key 是不可枚举的(没有方法能给出所有的 key)。

Vue3 使用WeakMap维护响应对象与原始对象之间的关联关系,避免通过对象构建多个响应对象

WeakSet

WeakSet 对象允许你将弱引用对象存储在一个集合中,并且元素只能是对象,而不能像 Set 可以是任何类型的任意值。如果没有其他对 WeakSet 中对象的引用,那么这些对象会被当成垃圾回收掉。

Symbol

symbol 是一种基本数据类型(primitive data type)。Symbol() 函数会返回 symbol 类型的值,且返回 symbol 值都是唯一的。

symbol 类型具有静态属性和静态方法。静态属性会暴露几个内建的成员对象;静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持new 关键字进行调用 new Symbol()

Vue3 使用symbol定义一些内置字段

废话了这么多,该开始看源码了,冲!!!

先看 reactive.ts,核心方法 createReactiveObject


createReactiveObject

reactive

vue3 使用reactive来包装响应数据,进行数据劫持,返回一个对象的响应式代理。

image.png

shallowreactive

reactive() 的浅层作用形式,不会进行深层级的转换:一个浅层响应式对象里只有根级别的属性是响应式的。属性的值会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

const state = shallowReactive({ foo: 1, nested: { bar: 2 } })
// 更改根级别的属性是响应式的
state.foo++
// ...但下层嵌套对象nested不会被转为响应式
isReactive(state.nested) // false 
// 不是响应式的
state.nested.bar++

image.png

readonly

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。只读代理是深层的:对任何嵌套属性的访问都将是只读的。

image.png

shallowreadonly

readonly() 的浅层作用形式,不会深层级的转换:只有根层级的属性变为了只读。属性的值都会被原样存储和暴露,这也意味着值为 ref 的属性不会被自动解包了。

image.png

不难发现,这几个api都基于 createReactiveObject 实现,先看一下这个方法定义

createReactiveObject 函数定义:

function createReactiveObject(
  target: Target, // 对象原值
  isReadonly: boolean, // 只读
  baseHandlers: ProxyHandler<any>, // 基础handler-proxy代理handler
  collectionHandlers: ProxyHandler<any>, // 私有handler-proxy代理handler
  proxyMap: WeakMap<Target, any> // 全局 WeakMap 集合
) {
    ...
}

先来看看

第一个参数 Target 的接口定义:

export interface Target {
  [ReactiveFlags.SKIP]?: boolean // 对象不可代理标记,markRaw() 会将其置为true
  [ReactiveFlags.IS_REACTIVE]?: boolean // reactive 标记,isReactive() 判断依据
  [ReactiveFlags.IS_READONLY]?: boolean // readonly 标记, isReadonly() 判断依据
  [ReactiveFlags.IS_SHALLOW]?: boolean // shallow reactive 标记,isShallow() 判断依据
  [ReactiveFlags.RAW]?: any // 对象原值, toRaw() 返回
}

再看看第三、第四参数,作为proxy的handlers,根据对象类型使用不同的拦截器,通过方法targetTypeMap判断对象类型来确认是使用baseHandlers 还是 collectionHandlers

// 规则-js数据类型 转 响应式对象类别
function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON // object/array 
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION // map/set/weakMap/weakSet
    default:
      return TargetType.INVALID
  }
}

createReactiveObject 实现:

function createReactiveObject() {
  // 一些类型判断,返回对象本身
  // 判断对象是否已经存在对应 proxy代理对象,通过对应的全局 weakMap 键值对判断
  // 这四种类型会对应 四个全局 weakMap 对象
  // reactiveMap、shallowReactiveMap、readonlyMap、shallowReadonlyMap
  ...
  // 声明proxy代理对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy) // 注册对象/proxy代理对象到对应的全局 weakMap,用于判断是否已经存在该对象代理
  return proxy
}

不难看出,主要差异其实就是构建proxy对象的 hander 参数,我们接着看:

  • reactive
    • mutableHandlers
    • mutableCollectionHandlers
  • shallow reactive
    • shallowReactiveHandlers
    • shallowCollectionHandlers
  • readonly
    • readonlyHandlers
    • readonlyCollectionHandlers
  • shallow readonly
    • shallowReadonlyHandlers
    • shallowReadonlyCollectionHandlers

先看 baseHandlers 差异(packages/reactivity/baseHandlers.ts):

mutableHandlers 实现:

export const mutableHandlers: ProxyHandler<object> = {
  get = createGetter(),
  set = createSetter(),
  deleteProperty,
  has,
  ownKeys
}

shallowReactiveHandlers 实现:

export const shallowReactiveHandlers = /*#__PURE__*/ extend(
  {},
  mutableHandlers,
  {
    get: shallowGet = createGetter(false, true),
    set: shallowSet = createSetter(true)
  }
)

readonlyHandlers 实现:

export const readonlyHandlers: ProxyHandler<object> = {
  get: readonlyGet = createGetter(true),
  set(target, key) {
    if (__DEV__) {
      warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  },
  deleteProperty(target, key) {
    if (__DEV__) {
      warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      )
    }
    return true
  }
}

shallowReadonlyHandlers 实现:

export const shallowReadonlyHandlers = /*#__PURE__*/ extend(
  {},
  readonlyHandlers,
  {
    get: shallowReadonlyGet = createGetter(true, true)
  }
)

实现 baseHandlers 的主要方法其实是 createGettercreateSetter,我们具体看看

createGetter 实现:

// @param isReadonly 是否是只读
// @param shallow 是否是浅形式
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // key 为 Target 内置字段,特殊处理
    // key === ReactiveFlags.IS_REACTIVE
    // key === ReactiveFlags.IS_READONLY
    // key === ReactiveFlags.IS_SHALLOW
    // key === ReactiveFlags.RAW
    ...

    // 数组类型判断
    const targetIsArray = isArray(target)
    // arrayInstrumentations 拓展数组常用7个方法,对方法进行监听,返回 Record<string, Function> 数据结构
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 如果使用数组函数,返回对应扩展函数
      return Reflect.get(arrayInstrumentations, key, receiver)
    }
    
    const res = Reflect.get(target, key, receiver)
    
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      // key 为symbol,判断是否为对象内置属性,或者时不需要构建响应式的属性
      return res
    }

    if (!isReadonly) {
      // track收集依赖
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
      // 浅形式,直接返回结果,不进行递归处理
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      // 值为ref,如果对象是数组,且使用索引值[index:number],直接返回ref对象,否则自动解包(.value)。这是官方api文档提示过访问到某个响应式数组的ref元素,不会自动解包。
      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
  }
}

createSetter 实现:

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      // 只读属性,不能修改
      return false
    }
    if (!shallow) {
      // 如果有属性是ref值,需要解包,使用原值做对比
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(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是否存在
    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
  }
}

接着来看 collectionHandlers 差异(packages/reactivity/collectionHandlers.ts):

mutableCollectionHandlers 实现:

export const mutableCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, false)
}

shallowCollectionHandlers 实现:

export const shallowCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(false, true)
}

readonlyCollectionHandlers 实现:

export const readonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(true, false)
}

shallowReadonlyCollectionHandlers 实现:

export const shallowReadonlyCollectionHandlers: ProxyHandler<CollectionTypes> = {
  get: /*#__PURE__*/ createInstrumentationGetter(true, true)
}

可以看出,collectionHandlers 都是由工厂函数 createInstrumentationGetter 创建,返回带getter的handler。实际上从源码中可以提出以下四个对象instrumentations,带有collection对象常用api字段(get / set / has / add / delete / clear / size / forEach / keys / values / entries)的扩展方法(属性)。如果访问对象字段key在instrumentations有声明定义,则使用instrumentations 对应声明进行拦截控制,否则正常返回。通过这四个对象区分四种 handlers 的 getter的拦截差异。

  • mutableInstrumentations
  • shallowInstrumentations
  • readonlyInstrumentations
  • shallowReadonlyInstrumentations
const mutableInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false)
}

const shallowInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, false, true)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, true)
}

const readonlyInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key, true)
    },
    get size() {
      return size(this as unknown as IterableCollections, true)
    },
    has(this: MapTypes, key: unknown) {
      return has.call(this, key, true)
    },
    add: createReadonlyMethod(TriggerOpTypes.ADD),
    set: createReadonlyMethod(TriggerOpTypes.SET),
    delete: createReadonlyMethod(TriggerOpTypes.DELETE),
    clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
    forEach: createForEach(true, false)
}

const shallowReadonlyInstrumentations: Record<string, Function> = {
    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)
}
// 扩展'keys', 'values', 'entries',以及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
    )
})

涉及方法太多,这里就简单拿几个来看:

get 实现:

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]
  const rawTarget = toRaw(target)
  const rawKey = toRaw(key)
  if (!isReadonly) {
    // 收集依赖
    if (key !== rawKey) {
      // 键是响应对象
      track(rawTarget, TrackOpTypes.GET, key)
    }
    track(rawTarget, TrackOpTypes.GET, rawKey)
  }
  const { has } = getProto(rawTarget)
  // 根据类型对value递归构建响应
  const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
  if (has.call(rawTarget, key)) {
    // 不会对ref自动解包。官方api文档提到访问 `Map` 这样的原生集合类型中的 ref 元素时不会自动解包。
    return wrap(target.get(key))
  } else if (has.call(rawTarget, rawKey)) {
    // 不会对ref自动解包
    return wrap(target.get(rawKey))
  } else if (target !== rawTarget) {
    // #3602 readonly(reactive(Map))
    // ensure that the nested reactive `Map` can do tracking for itself
    // 确保嵌套的响应式Map对象可以自行监听响应。
    // 如果先声明 reactive(Map) 并为在代码中引用字段所以没有触发 reactive的`getter`,而是直接赋值给readonly声明只读代理,只使用只读代理进行访问,只触发readonly的`getter`,导致后面如果直接对reactive setter 一个新字段后,readonly并不会收到更新操作。
    target.get(key)
  }
}

set 实现:

function set(this: MapTypes, key: unknown, value: unknown) {
  // 自动获取原始数据
  value = toRaw(value)
  const target = toRaw(this)
  const { has, get } = getProto(target)

  let hadKey = has.call(target, key)
  if (!hadKey) {
    // 自动获取原始数据
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }

  // 获取旧值,触发getter。如果是readonly(reative(Map)),且key是新字段,就会走入第三个条件,对新字段进行tracking
  const oldValue = get.call(target, key)
  target.set(key, value)
  // 通知更新
  if (!hadKey) {
    trigger(target, TriggerOpTypes.ADD, key, value)
  } else if (hasChanged(value, oldValue)) {
    trigger(target, TriggerOpTypes.SET, key, value, oldValue)
  }
  return this
}

add 实现:

function add(this: SetTypes, value: unknown) {
  // 自动获取原始数据
  value = toRaw(value)
  const target = toRaw(this)
  const proto = getProto(target)
  const hadKey = proto.has.call(target, value)
  if (!hadKey) {
    // add 新值,通知更新
    target.add(value)
    trigger(target, TriggerOpTypes.ADD, value, value)
  }
  return this
}

delete 实现:

function deleteEntry(this: CollectionTypes, key: unknown) {
  // 自动获取原始数据
  const target = toRaw(this)
  const { has, get } = getProto(target)
  // 判断字段是否存在
  let hadKey = has.call(target, key)
  if (!hadKey) {
    // 获取参数key原始数据
    key = toRaw(key)
    hadKey = has.call(target, key)
  } else if (__DEV__) {
    checkIdentityKeys(target, has, key)
  }

  const oldValue = get ? get.call(target, key) : undefined
  // forward the operation before queueing reactions
  // 删除字段
  const result = target.delete(key)
  if (hadKey) {
    // 字段存在,删除后触发更新
    trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
  }
  return result
}

其他(size / clear / createForEach / createReadonlyMethod / createIterableMethod)自行看源码

到这里,我们已经看完 reactive 基于 proxy 声明和拦截器的设置,接下来是响应式实现的两个核心方法 track()trigger()

文件:effect.tsdep.tseffectScope

track 依赖收集

从上述定义的hander拦截器函数中,可以看出在 getter 拦截器返回响应字段值之前,会调用 track(),对该响应字段进行依赖收集。

track过程,主要包括:响应式字段对应的依赖集合Dep的创建、与字段关联的副作用函数effect进行双向绑定。这样就构建好字段和副作用函数之间的依赖关系。

这里需要了解几个对象:

  • targetMap:一个响应对象target 对应 一个依赖映射Map<any, KeyToDepMap>
  • KeyToDepMap:一个响应字段key 对应 一个依赖集合Dep<Set>
  • effectStack:存放当前正被调用的副作用effect的栈。当一个副作用在执行前会被压入栈中,而在结束之后会被推出栈。
  • activeEffect:当前正在执行的副作用,或者也可以理解为effectStack的栈顶元素。
/**
 * @param target 响应对象
 * @param type 触发依赖收集类型
 * @param key 响应字段
 */
export function track(target: object, type: TrackOpTypes, key: unknown) {
  // shouldTrack 是否可以收集依赖标记
  // activeEffect 当前正在执行的副作用effect函数
  if (shouldTrack && activeEffect) {
    // 获取响应对象对应的依赖映射 deps<Map>(维护各个响应字段 与 对应的dep实例 的一一对应关系)
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 如果不存在,则为响应对象创建一个对应的 依赖映射 deps<Map>
      targetMap.set(target, (depsMap = new Map()))
    }
    // 从依赖映射 deps<Map> 获取响应字段对应的 依赖集 dep<Set>
    let dep = depsMap.get(key)
    if (!dep) {
      // 如果不存在,则为响应字段创建一个对应的 依赖集合 dep<Set> 
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined

    // 初始化完成,进行 依赖收集,主要是字段对应的 副作用函数effect 与 依赖集合dep<Set> 之间的关联
    trackEffects(dep, eventInfo)
  }
}

/**
 * @param dep<Set> 响应字段对应的 依赖集合 
 * @param debuggerEventExtraInfo 响应钩子信息集合
 */
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // dep 与 effect是否需要进行依赖收集操作标记
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    // 当前递effect栈大小 不超过阈值(30)
    // 且当前依赖集合 dep<Set> 是新创建的
    if (!newTracked(dep)) {
      // 标记新dep
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep) // 当前依赖集合 dep<Set>所关联的副作用函数effect还未被执行,是-true,否-false
    }
  } else {
    // Full cleanup mode. -
    // 当前递effect栈大小 超过阈值(30) 
    // 且依赖集合dep<set>不存在当前effect - run中执行cleanupEffect
    shouldTrack = !dep.has(activeEffect!)
  }

  if (shouldTrack) {
    // 需要进行收集
    // 进行双向绑定
    dep.add(activeEffect!) // 添加依赖:dep - effect
    activeEffect!.deps.push(dep) // 添加依赖:effect - dep
    // 触发钩子函数
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack({
        effect: activeEffect!,
        ...debuggerEventExtraInfo!
      })
    }
  }
}

trigger 依赖更新

从上述定义的hander拦截器函数中,可以看出在 setter 拦截器对响应字段进行复制后,会调用 trigger(),对该响应字段关联的依赖进行更新通知。

trigger 过程,主要根据响应对象以及触发更新的响应字段对应的依赖集合中,收集与该字段相关的所有副作用函数,然后在触发副作用函数的执行(直接执行或者加入调度队列等待执行)。

/**
 * @param target 响应对象
 * @param type 触发依赖更新类型
 * @param key 响应字段
 * @param newValue 新值
 * @param oldValue 旧值
 * @param oldTarget 
 * @returns 
 */
export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  // 获取响应对象对应依赖映射
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }

  // 需要更新的依赖集合
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 清空操作,触发所有依赖
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    // 监听数组.length 修改
    const newLength = toNumber(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= newLength) {
        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
    // 影响 length长度,键keys/值values迭代器,需要附带依赖更新
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          // 新元素,附带触发 iterate / map key interate 相关依赖更新
          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
          // 新元素,附带触发 length 相关依赖更新
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          // 删除元素,附带触发 iterate / map key interate 相关依赖更新
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        // 更新元素,附带触发 iterate 相关依赖更新
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  // 响应更新钩子信息
  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined

  // 执行依赖对应的副作用函数effect
  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        // deps[0] = dep = <Set<ReactiveEffect>>
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    // dep = Set<ReactiveEffect>
    for (const dep of deps) {
      if (dep) {
        // 收集dep关联的effect
        effects.push(...dep)
      }
    }
    // createDep 合并依赖
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  // 分发依赖更新 - 执行关联的副作用
  // [...dep] 结构 set 得到 ReactiveEffect[]
  const effects = isArray(dep) ? dep : [...dep]
  for (const effect of effects) {
    if (effect.computed) {
      // 先执行 computed依赖
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      // 后执行 其他依赖
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    // 副作用函数不是堆栈头 或 允许递归更新(如 watch 中修改响应字段,又会触发响应字段的依赖更新)
    if (__DEV__ && effect.onTrigger) {
      // 触发trigger钩子
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    if (effect.scheduler) {
      // 指定调度方法,执行调度方法 - computed/deferredComputed
      effect.scheduler()
    } else {
      // 执行副作用函数
      effect.run()
    }
  }
}

前面说到了一大堆的副作用函数,什么是副作用函数?

先看副作用函数工具方法effect,用于构建副作用函数对应的对象,同时执行副作用函数用于开启收集依赖,返回副作用函数执行代理方法run。

effect 定义:

/**
 * @param fn 副作用函数函数
 * @param options 配置选项
 * @returns ReactiveEffectRunner 副作用函数对象执行方法
 */
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {
  // 副作用函数已经声明对应的副作用函数对象,获取原函数
  if ((fn as ReactiveEffectRunner).effect) {
    fn = (fn as ReactiveEffectRunner).effect.fn
  }

  // 声明对应的副作用函数对象
  const _effect = new ReactiveEffect(fn)
  if (options) {
    // 合并配置项
    extend(_effect, options)
    // 关联scope - 理解为作用域
    // 全局维护了一次 activeEffectScope ,作为当前组件作用域,用户同一组件下effect的维护
    if (options.scope) recordEffectScope(_effect, options.scope)
  }
  // 如果没有配置lazy,默认执行一次副作用函数,用来收集相关依赖
  if (!options || !options.lazy) {
    _effect.run()
  }
  // 返回副作用函数执行代理run方法,同时挂载对应的作用函数对象
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

ReactiveEffect类声明:

export class ReactiveEffect<T = any> {
  active = true // effect 状态,默认true,如果被副作用函数被stop则为设置为false,后续执行run只会执行副作用函数fn
  deps: Dep[] = [] // 关联依赖 - 与 dep是多对多关系
  parent: ReactiveEffect | undefined = undefined // 父级effect,effect执行run过程遇到新的effect,会把当前effect压入堆栈stack,执行新的effect。parent在新的effect出栈后还原原来的effect,此过程用activeEffect变量记录当前执行的effect。

  /**
   * Can be attached after creation
   * @internal
   */
  computed?: ComputedRefImpl<T> // 关联计算属性实例
  /**
   * @internal
   */
  allowRecurse?: boolean // effect允许自身递归
  /**
   * @internal
   */
  private deferStop?: boolean // stop 标记,如果effect在执行run方法时执行stop,会先标记 deferStop=true,等到run执行完后重新执行stop

  // 钩子函数
  onStop?: () => void
  // dev only
  onTrack?: (event: DebuggerEvent) => void
  // dev only
  onTrigger?: (event: DebuggerEvent) => void

  constructor(
    public fn: () => T,
    public scheduler: EffectScheduler | null = null,
    scope?: EffectScope
  ) {
    recordEffectScope(this, scope) // 关联scope
  }

  // 副作用函数执行代理方法
  run() {
    // 如果effect被stop,active = false,只需要执行fn即可
    if (!this.active) {
      return this.fn()
    }
    // 判断当前effect是否已经在effect栈中
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack // 缓存当前状态
    while (parent) {
      if (parent === this) {
        return // 如果已经存在,退出run,保证只执行一次
      }
      parent = parent.parent
    }
    // 入栈
    try {
      this.parent = activeEffect // 缓存栈顶
      activeEffect = this // 设置当前effect为栈顶(入栈)
      shouldTrack = true // 允许track 收集依赖

      // 副作用函数执行,深度标记 +1
      trackOpBit = 1 << ++effectTrackDepth // effect 递归长度(栈大小)+1

      if (effectTrackDepth <= maxMarkerBits) {
        // 修改与effect依赖的dep标识 wasTracked - 已执行run
        initDepMarkers(this)
      } else {
        // 栈大小超过阈值30,从effect关联的dep中删除此effect(删除绑定)
        cleanupEffect(this)
      }
      return this.fn() // 执行副作用 - 收集依赖
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this) // 重置dep标记 wasTracked=0; newTracked=0
      }

      // 副作用函数执行完成,深度标记 -1
      trackOpBit = 1 << --effectTrackDepth

      activeEffect = this.parent // (出栈)
      shouldTrack = lastShouldTrack // 还原状态
      this.parent = undefined

      if (this.deferStop) {
        // 如果执行run中被stop,需要在重新stop
        this.stop()
      }
    }
  }

  stop() {
    // stopped while running itself - defer the cleanup
    // 如果effect在执行时 被stop,标记 deferStop 等待执行完run再执行stop
    if (activeEffect === this) {
      this.deferStop = true
    } else if (this.active) {
      // 从effect关联的dep中删除此effect(删除绑定)
      cleanupEffect(this)
      // 触发 onStop 钩子
      if (this.onStop) {
        this.onStop()
      }
      // 状态标记
      this.active = false
    }
  }
}

ReactiveEffectRunner 类型定义:

export interface ReactiveEffectRunner<T = any> {
  (): T
  effect: ReactiveEffect
}

这里补充一个 recordEffectScope,用户登记声明的effect对象到对应的组件作用域scope

export function recordEffectScope(
  effect: ReactiveEffect,
  scope: EffectScope | undefined = activeEffectScope
) {
  if (scope && scope.active) {
    scope.effects.push(effect)
  }
}

activeEffectScope:全局维护的一个字段,记录当前组件作用域,同另一个全局字段currentInstance 同步。

currentInstance: 字段记录当前组件实例,每一个组件都声明一个instance.scope<EffectScope> 字段,作为组件的作用域,收集与组件关联的所有effect。

// effect 作用范围
export class EffectScope {
  /**
   * @internal
   */
  active = true // 作用域生效状态
  /**
   * @internal
   */
  effects: ReactiveEffect[] = [] // 同范围的effect
  /**
   * @internal
   */
  cleanups: (() => void)[] = [] // 清空effect

  /**
   * only assigned by undetached scope
   * @internal
   */
  parent: EffectScope | undefined // 父作用域 - 父组件
  /**
   * record undetached scopes
   * @internal
   */
  scopes: EffectScope[] | undefined 
  /**
   * track a child scope's index in its parent's scopes array for optimized
   * removal
   * @internal
   */
  private index: number | undefined

  constructor(public detached = false) {
    ...
  }

  run<T>(fn: () => T): T | undefined {
    ...
  }

  /**
   * This should only be called on non-detached scopes
   * @internal
   */
  on() {
    activeEffectScope = this
  }

  /**
   * This should only be called on non-detached scopes
   * @internal
   */
  off() {
    activeEffectScope = this.parent
  }

  stop(fromParent?: boolean) {
    ...
  }
}

到这里,reactive 以及 响应收集、响应依赖以及阅读完了,但是总感觉缺少了点什么,整体功能还有点零散。这里说的副作用函数到底是什么?与响应字段有什么关联?还是一头雾水【已秃】

先继续阅读 RefComputed 的源码,看看能不能找到联系,把整个理解串联起来。

进入下一篇