Reactivity of the core

preface

This section code demonstration address, with the source code to eat more appetizing oh!

The core of Vue3 is the change of the responsive mechanism. What is the difference between Vue2 and Vue3?

  • More granular, add static nodes, event listening cache mechanism
    • The Diff algorithm will not go to patch
  • Use proxy instead of Object.defineProperty()
    • The pros and cons of the two
    • For example, there are as many as 13 interception methods of proxy, and objects can be monitored one by one. Object.defineproperty () is for the entire Object. , etc.

The illustration

Read the article in 12 minutes.

Implementation steps

Vue3 responsiveness is to collect dependencies on get(value operation) and trigger dependencies on SET (assignment operation).

We are currently implementing only the core, not the edge cases

Create a proxy to proxy data

The single test that needs to be implemented

// effect.spec.ts
it('effect'.() = > {
    // Reactive core: get collects dependencies set triggers dependencies
    const user = reactive({
      age: 10})});Copy the code
export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)
      // Proxy should be used with Reflect
      The receiver parameter in reflect. get preserves a reference to the correct reference to this (i.e. admin), which passes the correct object usage in reflect. get to get
      // No matter how the Proxy changes the default behavior, you can always get the default behavior in Reflect
      track(target, key)
      return res
    },
    set(target, key, value) {
      // Set returns true or false
      The set() method should return a Boolean value.
      // Return true to indicate success.
      // In strict mode, if the set() method returns false, a TypeError exception is raised.
      const res = Reflect.set(target, key, value)
      trigger(target, key)
      return res
    }
  })
}
Copy the code

Collect rely on

Vue3 is used to collect FN related to responsive variables through effect function.

The single test that needs to be implemented

describe('test effect'.() = > {
  it('effect'.() = > {
    const user = reactive({
      age: 10
    })
    let nextAge
    effect(() = > {
      nextAge = user.age + 1
    })
    expect(nextAge).toBe(11)}); });Copy the code

Implements the effect function and executes the function passed in.

export class ActiveEffect {
  private _fn: any
  constructor(fn) {
    this._fn = fn
  }
  run() {
    this._fn()
  }
}

export function effect(fn) {
  // Provide multiple properties and methods by constructing activeEffects
  const _effect = new ActiveEffect(fn)
  _effect.run()
}
Copy the code

When nextAge = user.age + 1, get the user. Age value and set it to nextAge.

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      // Get the user. Age value
      const res = Reflect.get(target, key)
      // Collect dependencies
      track(target, key)
      return res
    },
  })
}
Copy the code

Track and Vue3 collect fn and variables through Map and Set. The nice thing about a Map is that keys can be objects, while a Set ensures that FN is unique.

An 🌰

// For example, you define a variable like this
const foo = reactive({num:1.age:18})
// In Vue3 it is saved like this
targetMap={
  {num:1.age:18}, {num:(fn1,fn2),
     age:(fn1,fn2)
  }
}
// If there are more than one, do as follows
targetMap(weakMap) = {
     target1(Map): {
       key1(dep_Set): (fn1,fn2,fn3...)
       key2(dep_Set): (fn1,fn2,fn3...)
     },
    target2(Map): { key1(dep_Set): (fn1,fn2,fn3...) key2(dep_Set): (fn1,fn2,fn3...) }},Copy the code
// Define the global variable targetsMap
// The advantage of WeakSet is that WeakSet objects are weak references
const targetsMap = new WeakSet(a)// Collect dependencies
export function track(target, key) {
  // reactive passes in an object {}
  // Collect relationships: targetsMap collects all dependencies and then each {} as a depsMap
  } dep = deP;} deP = deP
  let depsMap = targetsMap.get(target)
  // If it does not exist, initialize it first
  if(! depsMap) { depsMap =new Map()
    targetsMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if(! dep) { dep =new Set()
    depsMap.set(key, dep)
  }

  // Store a fn, which is the effect function passed in and executed
  // Use a global variable
  dep.add(activeEffect)
}

let activeEffect
export class ActiveEffect {...run() {
    // === New === =
    activeEffect = this}}Copy the code

Trigger rely on

We just implemented the dependency collection when trigger GET, now we implement the dependency collection when trigger set.

The single test that needs to be implemented

describe('test effect'.() = > {
  it('effect'.() = > {
    // update
    // user.age++ => user.age = use.age + 1
    user.age++
    expect(nextAge).toBe(12)}); });Copy the code
export function reactive(raw) {
  return new Proxy(raw, {
    ...
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      trigger(target, key)
      return res
    }
  })
}
Copy the code
// Trigger dependencies
// We just need to go through the corresponding dep, then iterate through fn, and all the dependency values will change
export function trigger(target, key) {
  let depsMap = targetsMap.get(target)
  let dep = depsMap.get(key)
  for (const effect of dep) {
    effect.run()
  }
}
Copy the code

conclusion

TDD single test driver learning, more effectively let you understand the mechanism of the operation. Vue3 also provides a number of tests in the source code. In this section, we briefly describe the process of collecting dependencies and triggering dependencies. The debugging code link is at the beginning of this article. Learning source code must think more, why would do so? What’s the good of that? And so on. Finally, I recommend you to learn Vue3 source code, you can follow the author of mini-vue3 together. If you want to join the group, please contact me in the comment section. No one to carry, difficult to walk! Someone with, twice the result!