preface

Reative in Vue 3.0 is the same as Vue. Observable, a global API provided in Vue 2.6 that makes an object responsive.

reactive Vue.observable
Based on theProxyimplementation Based on theObject.definePropertyimplementation
rightProxy objectsoperate Direct manipulationThe source object
Return a responsibleProxy objects Return a responsibleThe source object


Reactivity workflow

In Vue 3.0,reactivityIt is independent, has no dependencies, and can be used anywhere you want to do responsive data

Let’s take a look at the main workflow beyond the complex judgments implemented in the source code


Key method implementation

This paper mainly implements several key methods:

reactive

  • Create reactive objects inProxyDefined in theget 及 setThe catcher for the incomingThe source objectProxy objectsIntercept processing
  • getProperties captured to the current object are also objects and recurse
  • Definition based onWeakMap 的 reactiveMapManage proxy objects if passed inobjectIt’s already been recorded. Just go backThis objectProxy objectsIf not, follow the normal procedure
/** * Handler object, defining the catcher */
const handlers = {
  set(target, key) {
    Reflect.set(... arguments) trigger(target, key) },get(target, key) {
    track(target, key)
    return typeof target[key] === 'object'
      ? reactive(target[key])
      : Reflect.get(... arguments) }, }/** * define a reactive object and return a proxy object *@param {*} object* /
function reactive(object) {
  if (reactiveMap.has(object)) return reactiveMap.get(object)

  const proxy = new Proxy(object, handlers)

  reactiveMap.set(object, proxy)
  return proxy
}
Copy the code

effect

  • Side effects, created for administrationeffectThe stackeffectStackThat will beeffectPush the stack first for dependency collection, which is performed onceeffectAnd into thegetIn the capture phase,Capture is completedAfter enteringfinallyMove it off the stack
/** side effect function *@param {*} fn* /
function effect(fn) {
  try {
    // Pushes the effect that needs to be performed on the stack, which is used to depend on the relationship between keys during collection
    effectStack.push(fn)

    // Perform the effect to enter the proxy's GET intercept
    return fn()
  } finally {
    // The current effect is off the stack when dependency collection is complete and all get processes are completed
    effectStack.pop()
  }
}
Copy the code

track

  • effectData is triggered after executiongetCatcher, which is called during this processtrackDo dependency collection
  • definetargetMapIn order toWeakMapTo collect dependencies and manage target objectstargetAnd its counterpartkey 
  • The second layer is for administrationkeyAnd its counterparteffect, the above flowchart can see the data structure and hierarchical division
/** * rely on collection *@param {*} target
 * @param {*} key* /
function track(target, key) {
  // Initialize the dependency Map
  let depsMap = targetMap.get(target)
  if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}// The second dependency uses Set to store the effect corresponding to the key
  let dep = depsMap.get(key)
  if(! dep) { targetMap.get(target).set(key, (dep =new Set()))}// Save the effect from the current stack to the layer 2 dependency
  const activeEffect = effectStack[effectStack.length - 1]
  activeEffect && dep.add(activeEffect)
}
Copy the code

trigger

  • Changes in theeffectIs specified insetCapture the device during this processtriggerResponsible for executing the currenttarget 下 keyThe correspondingeffectTo complete the responsive process
/** * Triggers the response and executes effect *@param {*} target
 * @param {*} key* /
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (depsMap) {
    const effects = depsMap.get(key)
    effects && effects.forEach(run= > run())
  }
}
Copy the code

computed

  • I’m going to take the rough and easy approach here and just return oneeffect 
/** * Compute attributes *@param {*} fn 
 */
function computed(fn) {
  return {
    get value() {
      return effect(fn)
    },
  }
}
Copy the code


Results show

Object properties are responsive, nested at multiple levels

const object = {
  o: {
    a: 1}}const proxy = reactive(object)

effect(() = > {
  console.log(`proxy.o.a: ${proxy.o.a}`)})Copy the code
  • The first call is printed once, the response is assigned again, and the call is calledeffect 

Responsive toner

  • Configure the responsive object, specify its RGB property, and check it outcomputed 
const object = {
  r: 0.g: 0.b: 0
}
const proxy = reactive(object)
const computedObj = computed(() = > proxy.r * 2)

effect(() = > {
  const { r, g, b } = proxy
  document.getElementById('r').value = r
  document.getElementById('b').value = b
  document.getElementById('g').value = g
  document.getElementById(
    'color'
  ).style.backgroundColor = `rgb(${r}.${g}.${b}) `
  document.getElementById('color_text').innerText = ` RGB:${r}.${g}.${b}`

  const { value } = computedObj
  document.getElementById(
    'computed_text'
  ).innerText = `computed_text: r*2=${value}`
})
Copy the code
  • Drag the three RGB ranges and their changes are reflected in the color blocks

  • Object dependencytargetMapStructure is as follows

  • The reactiveMap structure is as follows


conclusion

  • Through the above content, we can understand the responsive principle in Vue 3.0
    • reactiveCreate reactive objects
    • effectSide effects, call itself to collect dependencies, and call the function again when the data changes
    • trackDepend on the collection
    • triggerTrigger the corresponding in the dependencyeffect 
    • computedThe property is evaluated and called when its value changeseffect 
  • In the process can also be more familiar with some of the pre-basic knowledge
    • Proxy and reflection:Proxy Reflect 
    • JavaScript standard built-in objects:WeakMap Map Set 
    • Statement execution tips:try finally 

The resources