vue3 中的响应式 reactivity(一)

2 阅读7分钟

vue3 中的响应式 reactivity(一)

vue3源码分析太多,这里主要做一个学习笔记,也可以说是借鉴内容,兼听则明嘛。

1. ref

先看我们常用的ref函数

ref() --- ref.ts

export function ref(value?: unknown) {
  return createRef(value, false)
}

回调一个createRef 函数

createRef() --- ref.ts

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

看样子shallow是一个标识,到底是什么呢😅😅,看来ref实际是调用一个RefImpl类

class RefImpl --- ref.ts

class RefImpl<T> {
  private _value: T
  private _rawValue: T

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

  // 从构造函数可以看出 shallow 是一个用作标识是否 只是浅层调用的, 默认false,(默认会递归调用,全部进行响应式处理)
  constructor(
    value: T,
    public readonly __v_isShallow: boolean,
  ) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this); // 依赖跟踪, 进行Dep操作
    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, DirtyLevels.Dirty, newVal)
    }
  }
}

toRaw() --- reactive.ts

toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。 转存失败,建议直接上传图片文件

export function toRaw<T>(observed: T): T {
  const raw = observed && (observed as Target)[ReactiveFlags.RAW]
  return raw ? toRaw(raw) : observed
}
> toRaw 用于返回对象的原始值,如果 observed 是一个响应式代理对象,那就通过 ReactiveFlags.RAW 拿到其原始值,否则返回其本身:

// 官网示例
const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

triggerRefValue() --- ref.ts

看着很复杂, 实际就是判断ref对象是否有 dep数组,有直接触发triggerEffects(dep)😅

export function triggerRefValue(
  ref: RefBase<any>,
  dirtyLevel: DirtyLevels = DirtyLevels.Dirty,
  newVal?: any,
) {
  ref = toRaw(ref)
  const dep = ref.dep
  if (dep) {
    triggerEffects(
      dep,
      dirtyLevel,
      __DEV__
        ? {
            target: ref,
            type: TriggerOpTypes.SET,
            key: 'value',
            newValue: newVal,
          }
        : void 0,
    )
  }
}

triggerEffects() --- effect.ts

export function triggerEffects(
  dep: Dep,
  dirtyLevel: DirtyLevels,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
  pauseScheduling()

  // 依次执行 effect 队列中的 dep 
  // dep 中的 deps 数组存了该依赖的所有函数,会在此时被依次调用。(比如说watch, template中的更新都会存在deps里)
  for (const effect of dep.keys()) {
    // dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
    let tracking: boolean | undefined
    if (
      effect._dirtyLevel < dirtyLevel &&
      (tracking ??= dep.get(effect) === effect._trackId)
    ) {
      effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
      effect._dirtyLevel = dirtyLevel
    }
    if (
      effect._shouldSchedule &&
      (tracking ??= dep.get(effect) === effect._trackId)
    ) {
      if (__DEV__) {
        effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
      }
      effect.trigger()
      if (
        (!effect._runnings || effect.allowRecurse) &&
        effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
      ) {
        effect._shouldSchedule = false
        if (effect.scheduler) {
          queueEffectSchedulers.push(effect.scheduler)
        }
      }
    }
  }
  resetScheduling()
}

2. reactive

类似ref, 我们先从常用的reactive() 函数入手

reactive() --- reactive.ts

跟ref一模一样有没有,直接返回一个createReactiveObject的初始函数(函数柯里化),用户只用调一个参数就行

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() --- reactive.ts

其实就带使用es6的proxy,其中核心handler为 collectionHandlersbaseHandlers, 看看上面的create函数,是不是传了两个handler,mutableHandlers, mutableCollectionHandlers

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  // 只接收引用类型
  if (!isObject(target)) {
    if (__DEV__) {
      warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // 只支持 Object Array Set WeakSet Map WeakMap only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }

  // 前面一大堆东西只为这个, 其中Set Map WeakSet WeakMap 类型为collection
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )

  // 存到proxyMap中,防止重复proxy
  proxyMap.set(target, proxy)
  return proxy
}

class MutableReactiveHandler --- baseHandlers.ts

最常用,数组和普通对象的handler

export const mutableHandlers: ProxyHandler<object> = new MutableReactiveHandler()

get 处理 大段代码警告🥵

康康源码先

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

  get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      isShallow = this._isShallow

    // ReactiveFlags 是在reactive中声明的枚举值,如果key是枚举值则直接返回对应的布尔值
    // 如果为raw 直接返回 target

    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return isShallow
    } else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? isShallow
              ? shallowReadonlyMap
              : readonlyMap
            : isShallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        // receiver is not the reactive proxy, but has the same prototype
        // this means the reciever is a user proxy of the reactive proxy
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
        return target
      }
      // early return undefined
      return
    }

    // 这里就是对数组方法的特殊处理
    const targetIsArray = isArray(target)
    if (!isReadonly) {
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

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

    //如果 key 是 symbol 内置方法,或者访问的是原型对象,直接返回结果,不收集依赖
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 不是只读,就收集依赖 !!! 核心,只为这个,其他都是兼容边界条件
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 浅层直接返回
    if (isShallow) {
      return res
    }

    // 如果是ref, 不做递归reactive处理
    // 如果是arr[1]这种的就不解包,直接返回 res ,其他返回res.value 解除.value包装
    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    // 如果值是引用类型,递归调用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.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

核心有俩,一是递归调用reactive,实现深层响应式。二是track依赖

set 处理 大段代码警告🥵

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

  set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    let oldValue = (target as any)[key]
    if (!this._isShallow) {
      const isOldValueReadonly = isReadonly(oldValue)
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }

      // 不是数组, 旧值为ref 新值不为ref 直接赋值
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false
        } else {
          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)) {
      // trigger派发通知更新, 新值对象用add,旧值对象要set,
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }

核心为set, set的重点是,每次修改都会进行trigger操作

小结 reactive

从上面的代码看下来,handler的目的就是为了对被代理的对象进行 track 和 triiger处理

3. reactiveEffect

阐述了响应式是如何实现,其中核心为track 和 trigger两个函数。

track() --- reactiveEffect.ts

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (shouldTrack && activeEffect) {

    // 把需要跟踪的对象放到 depsMap 里 --- WeakMap
     let depsMap = targetMap.get(target)
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()))
    }

    // depMaps里存放的是dep --- Map,key是 被track 的对象
    // dep 里存放的是执行的, key值 是 被track的对象 key
    let dep = depsMap.get(key)
    if (!dep) {
      depsMap.set(key, (dep = createDep(() => depsMap!.delete(key))))
    }
    trackEffect(
      activeEffect,
      dep,
      __DEV__
        ? {
            target,
            type,
            key,
          }
        : void 0,
    )
  }
}

可以看出来,track的操作都是为了trackEffect🥶

课外知识 WeakMap,WeakMap的特点如下:

  1. key只能是Object,除了null之外,typeof obj === 'object' 的都可以当做key。
  2. WeakMap没有遍历的方法。
  3. WeakMap对key是弱引用,即垃圾回收机制不将该引用考虑在内 (gc机制为 浏览器 定期回收 不存在引用的的目标)

trigger() --- reactiveEffect.ts

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) {
    // 只有被tracked 的 才会触发trigger
    // never been tracked
    return
  }

  let deps: (Dep | undefined)[] = []
  if (type === TriggerOpTypes.CLEAR) {
    // set和map的 clear 操作,depsMap中所有dep都要triiger
    deps = [...depsMap.values()]
  } else if (key === 'length' && isArray(target)) {

    // eg:arr = [1,2,3,4], 操作为arr.lenght = 2, 
    // target 为arr ,key 为 length, newval 为 2
    // 只对 arr.length, arr[2], arr[3] 的 dep 推入deps 
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for SET | ADD | DELETE
    // key存在时,推入depsMap的deps
    if (key !== void 0) {
      deps.push(depsMap.get(key))
    }

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

  // 暂停调用栈
  pauseScheduling()

  // 对deps队列中的dep进行 triggerEffects 操作
  for (const dep of deps) {
    if (dep) {
      triggerEffects(
        dep,
        DirtyLevels.Dirty,
        __DEV__
          ? {
              target,
              type,
              key,
              newValue,
              oldValue,
              oldTarget,
            }
          : void 0,
      )
    }
  }

  // 重置调用栈
  resetScheduling()
}

同理嗷,trigger的操作是为了 triggerEffects

总结

本文以 ref()reactive() 函数为切入点,对vue3响应式原理进行分析,主要分析了reactivity目录下的 ref.ts reactive.ts baseHandler.ts 文件,ref在处理引用对象的时候其实还是调用了reactive方法,所以直接ref({})reactive({})区别不大,但是reactive有一个解包的过程,个人更喜欢reactive()。 vue3源码的函数调用一层套一层,让人看了欲罢不能。(终极套娃,一层套一层🥵)。

无论是ref 还是 reactive 目前都分析到trackEffect()triggerEffects()两个函数隶属于effect.ts,还有一些一笔带过的暂停调用栈和重置调用栈pauseScheduling() resetScheduling(),且看reactivity(二)。