Vue 3.2.26 source code interpretation (a) Reactivity responsive vUE 3.2.26 source code interpretation (two) initialize render Vue (three) Diff algorithm principle

Overall flow chart

demo

Look at this code with two questions in mind

  1. What is the return value of reactive?
  2. How does effect sense changes in internal responsive data?
test('base reactive'.() = >{
  const a = {
    bbb: 1
  };
  const ar = reactive(a)

  let dummy
  effect(() = > (
      dummy = ar.bbb
  ))
  expect(dummy).toBe(1)
  ar.bbb = 2;
  expect(dummy).toBe(2)})Copy the code

With these two questions in mind, let’s look at the core logic in the code

Reactive method

  1. Check whether it is read-only
  2. callcreateReactiveObjectReturns a reactive object
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.
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  ) 
}
Copy the code

createReactiveObject

  1. Verify that the agent object is valid
  2. Use global proxyMap to save proxy objects for next invocation
  3. Using a Proxy
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  / /... Check logic logic
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
Copy the code

The above delegate logic is written in baseHandlers to delegate ordinary objects, so let’s look at the code step by step.

Effect method

  1. createReactiveEffect
  2. Judgment isoptionsConfigure and whetherlazyDetermines whether to call it the first timeReactiveEffectIn therunmethods
  3. The specifiedrunIn the methodthisPoint to the
export function effect<T = any> (fn: () => T, options? : ReactiveEffectOptions) :ReactiveEffectRunner {

  / /... Check logic elision
  
  const _effect = new ReactiveEffect(fn)
  
  / /... Non-mainline logic elision
  
  if(! options || ! options.lazy) { _effect.run() }const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}
Copy the code

ReactiveEffect

  1. The constructorconstructorThree parameters are passed to thefn scheduler.scope, we focus on the first parameter
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []

  // can be attached after creationcomputed? :booleanallowRecurse? :booleanonStop? :() = > void
  // dev onlyonTrack? :(event: DebuggerEvent) = > void
  // dev onlyonTrigger? :(event: DebuggerEvent) = > void

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

  run() {
    try {
      effectStack.push((activeEffect = this)
      
      / /...
      
      return this.fn()
    } finally {
    
      / /...
      
      effectStack.pop()
      const n = effectStack.length
      activeEffect = n > 0 ? effectStack[n - 1] : undefined}}stop() {
    if (this.active) {
      cleanupEffect(this)
      if (this.onStop) {
        this.onStop()
      }
      this.active = false}}}Copy the code

Core logic in the run method:

  • The currenteffectAssigned toactiveEffectAnd pusheffectStack
  • performfn, the function passed in by the user.
  • resetactiveEffect.effectStackstate

When we execute fn, we trigger the GET operation on the AR object, so let’s see what we’re doing here

effect(() = > (
    dummy = ar.bbb
))
Copy the code

baseHandlers get

  1. Gets the currentkeyThe corresponding original object value
  2. Track the currentkeyThe dependence of
  3. Determines whether the original value is an object and needs to be returnedreactive
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
  
    / /...
    
    const res = Reflect.get(target, key, receiver)
    
    / /...
    
    if(! isReadonly) { track(target, TrackOpTypes.GET, key) }/ /...

    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}
Copy the code

Track dependence

  1. Create a dependency comparison table
  • eachobjCorresponds to a dependencyWeekMap
  • Object for eachkeyCorresponds to aSet
  • SetStore current inkeyThe correspondingeffect

const targetMap = new WeakMap<any, KeyToDepMap>()

export function track(target: object.type: TrackOpTypes, key: unknown) {
  let depsMap = targetMap.get(target)
  if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}let dep = depsMap.get(key)
  if(! dep) { depsMap.set(key, (dep = createDep())) } trackEffects(dep) }export constcreateDep = (effects? : ReactiveEffect[]):Dep= > {
  const dep = new Set<ReactiveEffect>(effects) as Dep
  dep.w = 0
  dep.n = 0
  return dep
}


export function trackEffects(dep: Dep, debuggerEventExtraInfo? : DebuggerEventExtraInfo) { dep.add(activeEffect!) activeEffect! .deps.push(dep) }Copy the code

Now that the dependency collection process is over, let’s look at the logic when we update

baseHandlers set

  1. Compare the old and new values to determine whether new attributes are added
  2. If yes, check whether the old and new values are the sametriggerlogic
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ) :boolean {
    
    / /...
    let oldValue = (target as any)[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
  }
}
Copy the code

Trigger update

  1. Get current dependenciesMap
  2. According to thekeyfromMapTo obtain the correspondingeffects
  3. Traverse the executioneffectsTo determineeffectsOn whether there isschedulerIf so, executeschedulerOtherwise dorunMethods.
export function trigger(
  target: object.type: TriggerOpTypes, key? : unknown, newValue? : unknown, oldValue? : unknown, oldTarget? :Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  
  / /...

  let deps: (Dep | undefined=) [] []/ /...
  
  deps.push(depsMap.get(key))
  
  / /...

  triggerEffects(deps[0])}export function triggerEffects(dep: Dep | ReactiveEffect[], debuggerEventExtraInfo? : DebuggerEventExtraInfo) {
  // spread into array for stabilization
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if(effect ! == activeEffect || effect.allowRecurse) {if (effect.scheduler) {
        effect.scheduler()
      } else {
        effect.run()
      }
    }
  }
}

Copy the code

Finally, let’s review the picture to see if it’s much clearer: