More articles

preface

Reactive and ref have been mentioned before, and effect is the next step. Effect can be understood as a dependency collection process, and can be seen in code (in simplified code, you can see in index1.html).

Simplify the code

Vue3-effect source location

Depend on the collection

Dependencies are collected in both Reactive and REF. The GET and SET phases trigger track/trackEffects and trigger/triggerEffects respectively

Reactive: reactive

// get
function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    if(! isReadonly) { track(target, TrackOpTypes.GET, key) } } }// set
function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ) :boolean {
    if (target === toRaw(receiver)) {
      if(! hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) }else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
  }
}
Copy the code

Ref:

export function trackRefValue(ref: RefBase<any>) {
  if (isTracking()) {
    ref = toRaw(ref)
    // Add dep attribute, Set type
    if(! ref.dep) { ref.dep = createDep() } trackEffects(ref.dep) } }export function triggerRefValue(ref: RefBase<any>, newVal? :any) {
  // DeP attribute exists, notifying dependency
  if (ref.dep) {
    triggerEffects(ref.dep)
  }
}
class RefImpl<T> {

  get value() {
    trackRefValue(this)}set value(newVal) {
    if (hasChanged(newVal, this._rawValue)) {
      triggerRefValue(this, newVal)
    }
  }
}
Copy the code

The get phase triggers track or trackEffects to collect dependencies, while the set phase triggers triggers triggers triggers or triggerEffects to notify the viewer. In effect, track will eventually trigger trackEffects and trigger will eventually trigger triggerEffects. Therefore, the interpretation of effect from track can basically complete the analysis of the whole process. The reason not to start with trigger is that there is no dependency that can trigger a dependency

track

Track code is as follows (after source code simplification) :

const targetMap = new WeakMap(a)let activeEffect

const createDep = (effects) = > {
  const dep = new Set(effects)
  return dep
}

function isTracking() {
  returnactiveEffect ! = =undefined
}

function track(target, type, key) {
  if(! isTracking())return
  // Whether there is a cache
  let depsMap = targetMap.get(target)
  // No cache storage, initialized to map
  if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}// Get whether the current attribute is cached
  let dep = depsMap.get(key)
  // No cache storage, initialized to set
  if(! dep) { depsMap.set(key, (dep = createDep())) } trackEffects(dep); }function trackEffects(dep) {
  // Does it exist
  letshouldTrack = ! dep.has(activeEffect)// There is no collection dependency
  if(shouldTrack) { dep.add(activeEffect); activeEffect.deps.push(dep); }}Copy the code

After some optimization, activeEffect was finally added to the DEP. The data store relationship is as follows: TargetMap

-> depsMap

-> dep

-> activeEffect

First, what does Effect do

function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn)
  // merge _effect exists
  if(options) extend(_effect, options)
  // Execute run initially
  _effect.run()

  const runner = _effect.run.bind(_effect)
  runner.effect = _effect
  // Return the runner so that the external can actively call run
  return runner
}
Copy the code

ReactiveEffect is instantiated and run is executed when an initial (or appropriate) call to Effect is made, and the current ReactiveEffect instance is assigned to activeEffect when run is executed (code in the ReactiveEffect section), Finally, the runner can be returned, not only when the data is updated, but also can be actively triggered at the appropriate time. Here involves ReactiveEffect, look at the implementation:

class ReactiveEffect {
  active = true
  deps = []

  constructor(fn, scheduler) {
    this.fn = fn
    // Customize scheduler if triggerEffects are triggered, otherwise fn is executed
    this.scheduler = scheduler
  }

  run() {
    // Assign the current instance to activeEffect
    activeEffect = this
    return this.fn()
  }

  stop() {
    if(this.active) {
      cleanupEffect(this)
      this.active = false}}}/ / clear the effect
function cleanupEffect(effect) {
  effect.deps.forEach((dep) = > dep.delete(effect))
  effect.deps.length = 0
}
function stop(runner) {
  runner.effect.stop();
}
Copy the code

Regardless of other considerations, the simplest process of track is as follows:

const targetMap = new WeakMap(a)class ReactiveEffect {
  active = true
  deps = []

  constructor(fn) {
    this.fn = fn
  }

  run() {
    activeEffect = this
    return this.fn()
  }
}

function effect(fn) {
  const _effect = new ReactiveEffect(fn)
  _effect.run()
  const runner = _effect.run.bind(_effect)
  return runner
}

function track(target, key) {
  let depsMap = targetMap.get(target)
  let dep = depsMap.get(key)
  trackEffects(dep);
}

function trackEffects(dep) {
  letshouldTrack = ! dep.has(activeEffect)if(shouldTrack) { dep.add(activeEffect); activeEffect.deps.push(dep); }}Copy the code

Scenario assumptions

For the simplest scenario, update the DOM with id=app:

const state = reactive({ a: 100 })

effect(
  () = > document.getElementById('app').innerText = state.a
)
Copy the code

When effect is called, fn is () => document.getelementById (‘app’).innertext = state.a, activeEffect is initialized. The current activeEffect is added to the DEP when state.a is called

If trigger triggers run in the activeEffect, the DOM can be updated as follows:

Get -> track -> Collect ActiveEffects

Set -> trigger -> Tells activeEffect

Next look at trigger

trigger

function trigger(target, type, key, newValue, oldValue) {
  // Get the cache
  const depsMap = targetMap.get(target)
  // No cache means that track is never triggered, so return directly
  if(! depsMap)return

  let deps = [];
  
  if(key ! = =void 0) deps.push(depsMap.get(key))
  const effects = []
  // Retrieve set data
  for (const dep of deps) {
    if(dep) { effects.push(... dep) } } triggerEffects(createDep(effects)) }function triggerEffects(dep) {
  for (const effect of isArray(dep) ? dep : [...dep]) {
    if (effect.scheduler) {
      effect.scheduler();
    } else{ effect.run(); }}}Copy the code

if (! DepsMap) return is an important action. If the value is not in the targetMap, track is not triggered, i.e. there is no observer (no notification object). Trigger will extract all effects and trigger triggerEffects. Scheduler is triggered when the developer passes in a scheduler, otherwise run -> fn is triggered

ref

The ref module adds a deP attribute and calls trackEffects or triggerEffects directly

Get -> trackEffects -> Collect activeEffects

Set -> triggerEffects -> Tell activeEffect

conclusion

With simplified code operation for easy understanding