Recently I am studying vue. js 3.0, where I record the relevant knowledge of reactive principle.

Principle of responsive systems

Vue.js 3.0 uses Proxy objects to implement the monitoring of attributes. There is no need to go through all the attributes during initialization and convert them to getters and setters via defineProperty. If there are multiple layers of nested attributes, the next level of attributes will only be recursively processed when an attribute is accessed, so reactive performance in VUe.js 3.0 is better than vue.js 2.x. By default, vue.js 3.0’s responsive system can listen for dynamically added properties, as well as for property deletions, and for array indexes and length operations. In addition, the vue.js 3.0 responsive system can be used as a separate module.

Simulation of the realization of responsive principle

Vue3 uses Proxy objects to rewrite the reactive system, which consists of the following functions:

  1. Reactive:
  • Receives a parameter and determines whether it is an object. If it is not an object, this parameter is returned without any response processing
  • Create an interceptor object handler and set get/set/deleteProperty
    • get
      • Collection dependencies (Track)
      • Returns the current key value.
      • If the value of the current key is an object, create an interceptor handler for the object of the current key and set get/set/deleteProperty
      • Returns the current key value if the current key value is not an object
    • set
      • If the new value is not equal to the old value, it will be updated to the new value and trigger the update.
    • deleteProperty
      • When the current object has the key, delete the key and trigger the update.
      • Return the Proxy object
  1. effect:
  • Take a function as an argument.
  • Collects dependencies when accessing properties of reactive objects
  1. track:
  • Receives two parameters: target and key
  • If there is no activeEffect, no effect dependency has been created
  • If activeEffect exists, judge whether there is target attribute in WeakMap set.
    • WeakMap set(target, (depsMap = new Map()))
    • If there is a target attribute in the WeakMap set, judge whether there is a key attribute in the – depsMap of the map value of the target attribute
      • Set(key, (dep = new set()))
      • Add this activeEffect if there is a key attribute in depsMap
  1. trigger:
  • Check whether there is target attribute in WeakMap
    • If there is no target attribute in WeakMap, there is no corresponding dependency of target
    • If there is a target attribute in WeakMap, it can judge whether there is a key attribute in the map value of target attribute. If there is, the collected effect() is triggered cycle.
const isObject = val= >val ! = =null && typeof val === 'object'
const convert = target= > isObject(target) ? reactive(target) : target
const hasOwnProperty = Object.prototype.hasOwnProperty
const hasOwn = (target, key) = > hasOwnProperty.call(target, key)

export function reactive (target) {
  // reactive receives a parameter and checks whether the parameter is an object or not.
  if(! isObject(target))return target

  // Create an interceptor object handler and set get/set/deleteProperty.
  const handler = {
    get (target, key, receiver) {
      // Call track to collect dependencies
      track(target, key)
      const result = Reflect.get(target, key, receiver)
      // If it is a nested attribute, the next-level attribute needs to be processed recursively
      return convert(result)
    },
    set (target, key, value, receiver) {
      const oldValue = Reflect.get(target, key, receiver)
      let result = true
      if(oldValue ! == value) { result =Reflect.set(target, key, value, receiver)
        // Call the trigger function to trigger the update
        trigger(target, key)
      }
      return result
    },
    deleteProperty (target, key) {
      const hadKey = hasOwn(target, key)
      const result = Reflect.deleteProperty(target, key)
      if (hadKey && result) {
        // Call the trigger function to trigger the update
        trigger(target, key)
      }
      return result
    }
  }
  // Return the Proxy object
  return new Proxy(target, handler)
}

let activeEffect = null // Store callback so that subsequent track functions can access the callback
export function effect (callback) {
  // effect takes a function as an argument
  activeEffect = callback
  callback() // Access responsive object properties to collect dependencies
  activeEffect = null // Set activeEffect to NULL if there are nested attributes when collecting dependencies
}

let targetMap = new WeakMap(a)// track() collects dependencies
// track receives two parameters: the target object and the attributes of the trace
export function track (target, key) {
  if(! activeEffect)return
  let depsMap = targetMap.get(target) // Find the dictionary, including attributes and the corresponding dep (i.e. function)
  if(! depsMap) {// If the current depsMap has no value, create a depsMap and add it to targetMap
    targetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key)
  if(! dep) { depsMap.set(key, (dep =new Set()))
  }
  dep.add(activeEffect)
}

// trigger() triggers the update, which needs to find the effect() attribute in targetMap to execute
export function trigger (target, key) {
  const depsMap = targetMap.get(target)
  if(! depsMap)return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect= > {
      effect()
    })
  }
}

// ref takes an internal value and returns a responsive, mutable ref object.
// The ref object has a single property.value pointing to an internal value.
// If an object is assigned a REF value, it is made highly responsive by means of reactive.
export function ref (raw) {
  // Determine whether raw is an object created by ref, and return it if so
  if (isObject(raw) && raw.__v_isRef) {
    return
  }
  let value = convert(raw)
  const r = {
    __v_isRef: true,
    get value () {
      track(r, 'value')
      return value
    },
    set value (newValue) {
      if(newValue ! == value) { raw = newValue value = convert(raw) trigger(r,'value')}}}return r
}
The toRefs function receives a reactive object returned by Reactive
// If the parameter passed is not a reactive object created by Reactive, it is returned directly
// All attributes of the passed object are then converted to objects similar to those returned by ref
// Mount the converted property to a new object
export function toRefs (proxy) {
  const ret = proxy instanceof Array ? new Array(proxy.length) : {}

  for (const key in proxy) {
    ret[key] = toProxyRef(proxy, key)
  }

  return ret
}
// Convert the passed argument to the object returned by ref
function toProxyRef (proxy, key) {
  const r = {
    __v_isRef: true,
    get value () {
      return proxy[key]
    },
    set value (newValue) {
      proxy[key] = newValue
    }
  }
  return r
}

// Takes a getter function and returns an immutable reactive ref object based on the return value of the getter.
export function computed (getter) {
  const result = ref()

  effect(() = > (result.value = getter()))

  return result
}
Copy the code

rective VS ref

  • Ref can convert basic data types into responsive objects. When retrieving data, the value attribute is used. The value can be omitted when used in templates. The object returned, reassigned to the object is also reactive.
  • Reactive returns an object that reassigns the lost response. The object returned cannot be deconstructed, and toRefs is used to process the object returned by Reactive