preface

Reactive has been written, so I’m going to write effect today. What is effect? Effect is the equivalent of Watcher in VUE2, but its implementation is easier to see and write, so let’s move on to the implementation

implementation

| - reactive | | - examples test folder -- reactive. HTML// Test the file
    |-- package.json
    |-- rollup.config.js  / / a rollup configuration
    |-- yarn.lock
    |-- core // Core code
        |-- index.js  / / the main file
        |-- reactive.js / / reactive file
        |-- proxyMap.js // Cache reactive data
        |-- utils.js // Tool file
        |-- baseHandlers.js // proxy function
        |-- effect.js / / effec file
Copy the code

To add reactive, add the effect.js file and export it in index.js

// index.js
export {
  effect
} from './effect'
Copy the code

If an effect is triggered when the data changes, we create a reactive effect

//effect.js
export const effect  = (fn, options) {
  // fn User-defined function, options
  // Make this effect reactive, so that data changes can be executed
   const effect =  createReactiveEffect(fn, options)
   if(! options.lazy) {// Lazy execution by default
     effect()
   }
   return effect

}

Copy the code

From the above code, you can see that we hand over the creation of a reactive effect to the createReactiveEffect function

let uid = 0
let activeEffect = undefined // Expose the effect
let effectStack = [] // Store the current effect stack
function createReactiveEffect (fn, options) {
    const effect = function reactiveEffect() {
      // Check whether the current effect is in the stack each time
      if(! effectStack.includes(effect)) {try {
          effectStack.push(effect) / / into the stack
          activeEffect = effect
          return fn() // Execute user-defined functions
        }finally {
          effectStack.pop() / / out of the stack
          activeEffect = effectStack[effectStack.length - 1]

        }
      }
    }
    effect.id = uid // Each effect has a unique identifier
    effect._isEffect = effect / / whether the effect
    effect.raw = fn / / function
    effect.options = options / / options options
    return effect
}
Copy the code

Now that we’ve created the effect, it’s time to invoke our effect when the dependency is triggered

// baseHandlers.js
import { trackOpTypes } from './operators'
import { track } from './effect'
function createGetter(isReadonly = false, shallow = false) {
/ / not just read
    if(! isReadonly) {// Trigger effect to collect dependencies
      track(target, TrackOpTypes.GET, key)
      
    }
}
Copy the code

What are TrackOpTypes? Create the Operators file for all the tags, at which point the directory is

| - reactive | | - examples test folder -- reactive. HTML// Test the file
    |-- package.json
    |-- rollup.config.js  / / a rollup configuration
    |-- yarn.lock
    |-- core // Core code
        |-- index.js  / / the main file
        |-- reactive.js / / reactive file
        |-- proxyMap.js // Cache reactive data
        |-- utils.js // Tool file
        |-- baseHandlers.js // proxy function
        |-- effect.js / / effec file
        |-- operators.js / / operators file
Copy the code

Operators Adds the TrackOpTypes type tag

// operators.js
export const TrackOpTypes = {
  GET : 0
}
Copy the code

The next important function is the track function. The main function of this function is to collect the effect function corresponding to the properties of each object

// effect.js

// Collect the effect function corresponding to the property in each object
Key: {name: 'XXX ', age: 18} value: (map) => (name => set age => set
export let targetMap = new WeakMap(a)export const track = (target, type, key) = > {
    // Check whether activeEffect exists
    if(activeEffect == undefined) {
      // No effect was created
      return
    }
    // Whether it already exists in targetMap
    let depsMap = targetMap.get(target)
    if(! depsMap) { targetMap.set(target, (depsMap =new Map))}let dep = depsMap.get(key)
    if(! dep) {// Set prevents repetition effect
      depsMap.set(key, (dep = new set()))
    }
    if(! dep.has(activeEffect)) { dep.add(activeEffect) } }Copy the code

Track already has the ability to trigger an effect when responsive data changes. We’ve already done that. Now we need to implement dependency updates

// baseHandlers.js
function createSetter(shallow = false) {
  return function set(target, key, value, receiver) {
    const res = Reflect.set(target, key, value, receiver)
    return res
  }
}
Copy the code

Next, make the changes to createSetter

import { hasOwn, isArray, isIntegerKey, isObject, hasChange } from "./utils"
import { trackOpTypes, TriggerOrTypes } from './operators'
import { track, trigger } from './effect'
function createSetter(shallow = false) {
  return function set(target, key, value, receiver) {
    // Get the old value
    const oldValue = target[key]
    // Check whether the key is a number or length
    const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length :  hasOwn(target, key)
    const res = Reflect.set(target, key, value, receiver)
    if(! hadKey) {/ / new
      trigger(target, TriggerOrTypes.ADD, key, value)
    }else if(hasChange(value, oldVal)) {
      / / modify
      trigger(target, TriggerOrTypes.SET, key, value, oldVal)
    }
    return res
  }
}
Copy the code

Utils added several new functions

// utils.js
export const isArray = Array.isArray
export const isIntegerKey = (key) = > parseInt(key) + ' ' === key
export const hasChange = (newValue, oldValue) = >newValue ! == oldValueexport const hasOwn = (target, key) = > Object.prototype.hasOwnProperty.call(target, key)
Copy the code

Operators Adds the TriggerOrTypes type tag

// operators.js
export const TriggerOrTypes = {
  ADD: 0.SET: 1
}
Copy the code

The trigger function is the focus of this discussion

// effect.js
// Find the effect of the property object
export const trigger = (target, type, key, value, newValue) = > {
 const add = (effectsToAdd) = > {
  effectsToAdd.forEach((effect) = > {
     effects.add(effect)
  })
 }
  // See if there is a target in targetMap
  let depsMap = targetMap.get(target)
  if(! depsMap)return;
  // Collect all effects into a collection
  const effects = new Set(a)// Set advantage: no repetition
  // Determine complex situations
  if(key === 'length' && isArray(target)) {
    // If the corresponding length, there is a dependency collection to update
    depsMap.forEach((dep,key) = > {
       if(key === 'length' || key > value) {
         add(dep)
       }
    })
  }else {
    // Non-array, object
    if(key ! =undefined) {
      add(depsMap.get(key))
    }
    // Whether to add
    switch(type) {
      case TriggerOrTypes.ADD:
        if(isArray(target) && isIntegerKey(key)) {
          add(depsMap.get('length'))
        }
   }
  }
  effects.forEach((effect) = > effect())
}
Copy the code

Vue3 source code implementation series

Vue3 source code – Reactive data Reactive

Vue3 source-effect relies on collecting triggered updates

Vue3 source code – ref article

Vue3 source code – toRef and toRefs