Vue3阅读源码系列(四):响应式原理(reactive、ref)

618 阅读5分钟

上一章我们知道了组件是如何挂载渲染的,但是留了一个问题:响应式数据是如何收集的effect对象? 这章我们就从reactive和ref两个声明响应式数据的API入手:

reactive

// packages/reactivity/src/reactive.ts
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers, // 普通对象的处理器
    mutableCollectionHandlers, // 集合对象的处理器
    reactiveMap
  )
}

实际调用的是createReactiveObject

createReactiveObject

// 创建响应式对象
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  if (!isObject(target)) { // 判断是否是object类型
    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
  // 如果已经被代理过 直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  // 如果已经存在代理对象,直接返回
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  // 只有特定的值类型才能被观察 不符合的直接返回
  if (targetType === TargetType.INVALID) {
    return target
  }
  // 创建代理对象
  const proxy = new Proxy(
    target,
    // 类型为集合的使用集合处理器,否则使用基础处理器 (集合类型:Map Set WeakMap WeakSet)
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

这里使用Proxy代理target,handler根据传入类型判断,我们一般使用baseHandlers,这里其实是传入的mutableHandlers。PS:不熟悉Proxy API的请先去熟悉哦

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has, // 处理 in 操作符
  ownKeys // 处理 Object.keys()、Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()、for...in
}

我们主要看get和set handler,这是我们最常用的获取和设置值时触发的代理执行,他们分别由createGetter和createSetter创建

createGetter

// packages/reactivity/src/baseHandlers.ts
// 创建get handler
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 一些边界处理
    ...
    // target是否是数组
    const targetIsArray = isArray(target)

    if (!isReadonly) {
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        // 数组的特殊处理 arrayInstrumentations为重写的数组方法对象
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }
    // 返回值处理
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

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

    if (shallow) {
      return res
    }

    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
      // 返回值是对象的情况下,执行reactive(递归操作)
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

这里的核心是track函数的执行,他会进行收集effect操作,我们来看具体的实现:

track
// packages/reactivity/src/effect.ts
export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {
    // targetMap是一个WeakMap实例
    let depsMap = targetMap.get(target)
    if (!depsMap) {
      // 没有depsMap则设置target为targetMap的键 值为一个Map
      targetMap.set(target, (depsMap = new Map()))
    }
    // 获取dep
    let dep = depsMap.get(key)
    if (!dep) {
      // 没有dep则设置key为depsMap的键 值为一个Set集合 这里放的就是key收集的effect集合
      depsMap.set(key, (dep = createDep()))
    }

    const eventInfo = __DEV__
      ? { effect: activeEffect, target, type, key }
      : undefined
    // 收集effect到dep中
    trackEffects(dep, eventInfo)
  }
}
trackEffects
export function trackEffects(
  dep: Dep,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  let shouldTrack = false
  if (effectTrackDepth <= maxMarkerBits) {
    if (!newTracked(dep)) {
      dep.n |= trackOpBit // set newly tracked
      shouldTrack = !wasTracked(dep)
    }
  } else {
    // Full cleanup mode.
    shouldTrack = !dep.has(activeEffect!)
  }
  // only track it if it's not already being tracked
  if (shouldTrack) {
    // 收集effect副作用 
    dep.add(activeEffect!)
    // effect 收集dep 为了cleanup时候清除
    activeEffect!.deps.push(dep)
    if (__DEV__ && activeEffect!.onTrack) {
      activeEffect!.onTrack(
        extend(
          {
            effect: activeEffect!
          },
          debuggerEventExtraInfo!
        )
      )
    }
  }
}

这里的逻辑也很简单,唯一的疑惑可能就是activeEffect从何而来,其实activeEffect就是我们使用ReactiveEffect类声明的实例,当我们执行effect.run时,它会将activeEffect赋值为effect。我们来看看具体的实现

class ReactiveEffect {
  ...
  run() {
    if (!this.active) {
      return this.fn()
    }
    let parent: ReactiveEffect | undefined = activeEffect
    let lastShouldTrack = shouldTrack
    while (parent) {
      if (parent === this) {
        return
      }
      parent = parent.parent
    }
    try {
      // 记录activeEffect
      this.parent = activeEffect
      // 将activeEffect赋值为this 这里的this指向实例 即我们实例化的effect
      activeEffect = this
      // 执行fn之前将shouldTrack设置为true
      shouldTrack = true

      trackOpBit = 1 << ++effectTrackDepth

      if (effectTrackDepth <= maxMarkerBits) {
        initDepMarkers(this)
      } else {
        cleanupEffect(this)
      }
      return this.fn()
    } finally {
      if (effectTrackDepth <= maxMarkerBits) {
        finalizeDepMarkers(this)
      }

      trackOpBit = 1 << --effectTrackDepth
      // activeEffect重新赋值为this.parent 这里配合try代码块中的操作来处理effect嵌套的情况
      activeEffect = this.parent
      shouldTrack = lastShouldTrack
      this.parent = undefined

      if (this.deferStop) {
        this.stop()
      }
    }
  }
  ...
}

到这里我们其实已经知道了响应式数据是如何收集的effect对象,当我们执行函数更新函数的时候,执行render,触发响应式数据的get,然后对应的key对应的dep就会收集到当前的effect。
响应式对象与dep在数据结构上的关系:vue3使用一个targetMap全局WeakMap实例来存储,他的键是我们的target,值是一个Map实例,改Map实例的键是target的键,值是对应的键收集的effect集合(Set)。
上述是收集的过程,下面看看触发的过程:

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) {
      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
    }
    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
    // 如果target是原型链中的某些东西,则不要触发
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // 没有key的情况下触发add
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 有key的情况下触发set
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

最终会触发trigger:

trigger
// 触发effect函数
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)
  if (!depsMap) {
    // never been tracked
    return
  }
  // 创建要执行的deps数组
  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // collection being cleared
    // trigger all effects for target
    // 清空数组或者map的时候触发所有key对应的的effect函数
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || key >= newLength) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    if (key !== void 0) {
      // set add delete操作 将key对应的effect函数添加到deps数组中
      deps.push(depsMap.get(key))
    }

    // also run for iteration key on ADD | DELETE | Map.SET
    // 根据不同的操作push对应的dep
    switch (type) {
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
          // 将循环操作的effect函数添加到deps数组中
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
      case TriggerOpTypes.DELETE:
        if (!isArray(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        }
        break
      case TriggerOpTypes.SET:
        if (isMap(target)) {
          deps.push(depsMap.get(ITERATE_KEY))
        }
        break
    }
  }

  const eventInfo = __DEV__
    ? { target, type, key, newValue, oldValue, oldTarget }
    : undefined
  // 执行triggerEffects 执行dep里所有的effect
  if (deps.length === 1) {
    if (deps[0]) {
      if (__DEV__) {
        triggerEffects(deps[0], eventInfo)
      } else {
        triggerEffects(deps[0])
      }
    }
  } else {
    const effects: ReactiveEffect[] = []
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__DEV__) {
      triggerEffects(createDep(effects), eventInfo)
    } else {
      triggerEffects(createDep(effects))
    }
  }
}
triggerEffects
export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  // spread into array for stabilization
  const effects = isArray(dep) ? dep : [...dep]
  // computed的effect会先执行
  // 防止render获取computed值得时候_dirty还没有置为true
  for (const effect of effects) {
    if (effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}
// 执行effect
function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    // 如果effect 有调度器 执行调度器 否则执行run 最终都会执行函数更新函数来进行更新
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}

组件的调度器使用了queueJob,他使用异步的方式来优化在一次同步更新中响应式数据多次改变触发多次函数更新函数执行的性能,使得函数更新函数只执行一次。这里不具体展开,有兴趣的可以看具体源码实现,其并不复杂。

ref

ref针对的是单个值的响应式处理,其更加简单,而且没有reactive响应式丢失的问题,我们看它的具体实现:

// packages/reactivity/src/ref.ts
export function ref(value?: unknown) {
  // 创建ref
  return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  // 实例化RefImpl类并返回
  return new RefImpl(rawValue, shallow)
}
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) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    // value也可以是复杂数据类型 会执行reactive API将其变成响应式
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    // 收集依赖
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      // 触发依赖
      triggerRefValue(this, newVal)
    }
  }
}

可以看到ref返回的是一个RefImpl实例,它使用get set存取器,在get中收集依赖,在set中触发依赖,与reactive不同的是ref的依赖集合保存在自身的dep属性,而不是全局的targetMap对象。接下来看看具体的trackRefValue和triggerRefValue操作:

trackRefValue

// packages/reactivity/src/ref.ts
export function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    if (__DEV__) {
      // 调用trackEffects
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}

最终调用trackEffects,这个函数我们在reactive的收集过程中也调用过,他们是公用。

triggerRefValue

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  const dep = ref.dep
  if (dep) {
    if (__DEV__) {
      // 触发effect
      triggerEffects(dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      triggerEffects(dep)
    }
  }
}

这里也是复用triggerEffects去执行收集的依赖