likes
comments
collection
share

[Vue 源码] Vue 3.2 - Reactive 原理

作者站长头像
站长
· 阅读数 13

代码运行结果

[Vue 源码] Vue 3.2  - Reactive 原理

代码示例

  <script src="./dist/reactivity.global.js"></script>
  <body>
    <div id="app"></div>
    <script>
      const { effect, reactive, ref, watch } = VueReactivity

      const state = reactive({ name: 'cyan', age: 18 })

      effect(() => {
        app.innerHTML = state.name + state.age
      })

      setTimeout(() => {
        state.name = 'tom'
      }, 1000)
    </script>
  </body>

挂载阶段

执行第一句代码:const state = reactive({ name: 'cyan', age: 18 })

第一:reactive 会通过调用 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 函数中 通过 proxy 和 baseHandlers 创建响应式对象。并将响应式对象返回给 state。

  const proxy = new Proxy(
    target,
    targetType = baseHandlers
  )
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {


 // 如果已经是一个响应式对象了,直接返回该响应式对象
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

 // 如果多次通过一个相同的对象,创建响应式对象,直接返回该响应式对象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 不支持简单值
  const targetType = getTargetType(target)

  if (targetType === TargetType.INVALID) {
    return target
  }
  
  // 创建响应式对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )

  proxyMap.set(target, proxy)
  return proxy
}

执行第二句代码 effect(() => {app.innerHTML = state.name + state.age})

第三:调用 effect 函数

  • 通过 new ReactiveEffect 来创建 ReactiveEffect 对象。
  • 执行 ReactiveEffect 对象上的 Run 方法,之后返回 run 方法,暴露给用户进行调度(可以用于批量更新)。
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {

  const _effect = new ReactiveEffect(fn)
  _effect.run()
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

第四:ReactiveEffect 类的 constructor 被调用, 将 effect 的参数作为 ReactiveEffect 对象的 fn 属性。

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

第五:调用 run 方法,当前 activeEffect 指向 effect 函数创建的 ReactiveEffect 对象,ReactiveEffect 对象的 fn 属性被调用, 调用过程中去访问了响应式对象 state(state.name + state.age),从而触发响应式对象/代理Proxy 对象的 getter 方法。

ReactiveEffect.fn = () => {
        app.innerHTML = state.name + state.age; 
 })

第六:在 state 的 getter 方法中

  • 先通过 Reflect.get 取值。
  • 调用 track 函数跟踪当前的 activeEffefcts 依赖对象。
  • 如果是一个引用类型值,继续去通过 reactive 递归创建响应式对象。
  • 返回 Reflect.get 的取值结果。
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    const res = Reflect.get(target, key, receiver)
    track(target, TrackOpTypes.GET, key)
    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
  }
}

第七:通过 track函数, trackEffects 函数建立双 Map 结构跟踪属性依赖。结果是这样的:我们访问了 state.name, state.age, 所以 name,age 对应的 映射列表里有 当前 activeEffects 也就是 ReactiveEffect 对象。

const targetMap = {
    {name: "cyan", age: 18}: {
        name: [ReactiveEffect],
        age: [ReactiveEffect]
    }
}

[Vue 源码] Vue 3.2  - Reactive 原理

图中还有一个细节没有表示,每一个 ReactiveEffefct 对象的 deps 属性,都指向该属性的依赖对象列表。用于每次 run 之后清理依赖对象。

第七:到这里为止,初始化完毕。app.innerHTML 中初始值渲染完成。

更新阶段:

第一:当执行 state.name = "tom" 时,会触发响应式对象的 settter 操作,在 settter 操作中

  • 通过 Reflect.set 改变响应式对象的值。
  • 然后通过 trigger 函数,触发 ReactiveEffects 依赖对象。
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
    }
    
    const result = Reflect.set(target, key, value, receiver)

    trigger(target, TriggerOpTypes.SET, key, value, oldValue)

    return result
  }

第二:通过 trigger, triggerEffects, triggerEffect 三个函数,来触发更新,通过 cleanEffects 清理上次的依赖对象, 执行每个 ReactEffect 依赖对象的 run 方法,重新调用 ReactEffcts 对象的 fn属性。也就是 effect 函数的第一个参数。 也就是 javascriptReactiveEffect.fn = () => {app.innerHTML = state.name + state.age; })

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  triggerEffects(createDep(effects), eventInfo)
}

export function triggerEffects(
  dep: Dep | ReactiveEffect[],
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  for (const effect of effects) {
    if (!effect.computed) {
      triggerEffect(effect, debuggerEventExtraInfo)
    }
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  effect.run()
}

第三:state.name 又去调用了响应式对象的 getter 方法,由于通过 Reflect.set 修改了值,所以再次访问就是最新值,同时 再次收集依赖,返回新值,等待下一次更新。

第四:至此更新完毕。