This is the first day of my participation in the First Challenge 2022

The opening

Recently in preparation for spring recruitment, and then saw a good tutorial, thinking of recording, incidentally also share with you. Use notes + personal understanding to urge yourself to learn. This series will continue to be updated, please like 👍

Full text 1800+,20 minutes will introduce you to the basic principles of Reactive, one of the three core components of VUE3, and deepen your understanding of dependency collection.

Take a look at the three versions of reactive

1. Go back to basics — V1

Let’s just think about it for a moment, if we wanted to implement a responsive data directly, whether we would re-execute the responsive data and some relationship that we’ve already established with the responsive data, and get the relationship again.

This may not be very clear (I am, too), but let’s look at a simple example.

let a = 10;
let b = a + 10  // b = a + 10 // b = a + 10

a = 20
b = a + 10 // Re-enforce the dependency
Copy the code

In the above code, A is a response data, and the value of B will be affected by A, b = a + 10; That’s the dependency between them

2. Encapsulate an update function –v2

If b = a + 10 should be executed every time the response object a changes, we should wrap an update function

let a = 10;
let b;
update()
function update () {
  b = a + 10
}

a = 20
update()

a = 30
update()
Copy the code

The update function stores the dependency between a and b, and executes this function manually every time we change the value of a. In fact, if the relationship between B and A is more complicated, this encapsulation is necessary.)

How do we implement a listener to help us do such a thing? Let’s see how this is implemented in VUE first

3. Effect and Reactive –v3 in Vue

So without further ado let’s get right to the code

const { reactive, effect } = require('@vue/reactivity')

let a = reactive({
  value: 10
})
let b;

effect(() = > {
  b = a.value + 10
  console.log(b)
})
a.value = 20
a.value = 30
a.value = 40
// output : 20 30 40 50
Copy the code

Vue does this very cleverly by implementing a function reactive that returns a reactive object and an effect function that passes in another function as an argument (later called a dependency function).

The dependency function holds the dependencies of A and B. It has the following characteristics

  • The function is executed once during initialization. (This is to establish the relationship between A and B)
  • This dependency function is re-executed later when reactive data A changes

Well, with the above three versions of the case clear, let’s implement a responsive system ourselves.

Handwritten responsive system

Implement a Dep class and a listener function effectWatch

The Dep class has two main responsibilities

  • Collect dependent functions that are executed once during initialization
  • When reactive data changes, the dependent function is re-executed

EffectWatch function is the main

  • Takes an argument that is a dependent function
  • Store the incoming dependency function temporarily and find a way for it to be collected by Dep

All right, let’s figure out what it’s supposed to do. Let’s just build a prototype of it

Look back at the right part of the picture

Let’s go ahead and implement our JS

let currentEffect; // Temporarily store dependencies
class Dep {
  constructor(val) {
    this._val = val
    this.effects = new Set(a)// will depend on storage
  }
  set value (value) {
    this._val = value
  }
  get value () {
    this.depend() // Collect dependencies while reading
    return this._val
  }
  // 1. Collect dependencies
  depend () {
    if(! currentEffect) {this.effects.add(currentEffect)
    }
  }

  // 2. Trigger dependencies
  notice () { }
}
function effectWatch (effect) {
  // Collect dependencies
  currentEffect = effect
  effect() // Initialize the execution
  currentEffect = null
}
Copy the code

Well, the prototype of the code is built, and also completed a certain function

Let’s start with a simple test

. .let dep = new Dep(10)
let b;
effectWatch(() = > {
  console.log('hello')
  b = dep.value + 10
})

// output: hello
Copy the code

The dependency collection function is executed for the first time, so let’s implement re-execution of the dependency function when the dep.value changes

Trigger rely on

class Dep{...// Notifying the notice method triggers a dependency when the value of a reactive object changes
 set value (value) {
    this._val = value
    this.notice() 
  }
// 2. Trigger dependencies
  notice () {
    this.effects.forEach(effect= > { // All collected dependencies are executed after the notice is executed
      effect()
    })
  }
...
}
Copy the code

Let’s do a test

.let dep = new Dep(10)
let b;
effectWatch(() = > {
  console.log('hello')
  b = dep.value + 10
})

dep.value = 40
//output : hello hello 

Copy the code

So far we have achieved a simple version of the reactive system, which corresponds to the REF in VUE. Now we write a reactive function based on the above basic data types to achieve the reactive processing of objects.

Go back and forth to the left side of the diagram above

Let’s look at the implementation code

const targetsMap = new Map(a)function getDep (target, key) {
  let depsMap = targetsMap.get(target)
  if(! depsMap) { depsMap =new Map()
    targetsMap.set(target, depsMap)
  }

  let dep = depsMap.get(key)
  if(! dep) { dep =new Dep()
    depsMap.set(key, dep)
  }
  return dep
}

function reactive (row) {
  return new Proxy(row, {
    get (target, key) {
      // target is the target object row
      // 1. Set each key in target to a responsive DEP key->dep
      let dep = getDep(target, key)
      // 2. Collect deP dependencies for this key
      dep.depend()
      return Reflect.get(target, key)  // Target [key]
    },
    set (target, key, value) {
      let dep = getDep(target, key)
      let result = Reflect.set(target, key, value)
      dep.notice()
      return result
    }
  })
}
Copy the code

All right, after we write the code we’ll do a simple test

.const user = reactive({
  age: 20
})
let doubleAge

effectWatch(() = > {
  doubleAge = user.age * 2
  console.log(doubleAge)
})

user.age = 21
user.age = 22

// output: 40 42 44

Copy the code

The results are as we expected, and reactive is almost done.

The complete code



let currentEffect; // Temporarily store dependencies
class Dep {
  constructor(val) {
    this._val = val
    this.effects = new Set(a)// will depend on storage
  }
  set value (value) {
    this._val = value
    this.notice()
  }
  get value () {
    this.depend() // Tell reactive objects to collect dependencies
    return this._val
  }
  // 1. Collect dependencies
  depend () {
    if (currentEffect) {
      this.effects.add(currentEffect)
    }
  }

  // 2. Trigger dependencies
  notice () {
    this.effects.forEach(effect= > {
      effect()
    })
  }
}
function effectWatch (effect) {
  // Collect dependencies
  currentEffect = effect
  effect() // Initialize the execution
  currentEffect = null
}

const targetsMap = new Map(a)function getDep (target, key) {
  let depsMap = targetsMap.get(target)
  if(! depsMap) { depsMap =new Map()
    targetsMap.set(target, depsMap)
  }

  let dep = depsMap.get(key)
  if(! dep) { dep =new Dep()
    depsMap.set(key, dep)
  }
  return dep
}

function reactive (row) {
  return new Proxy(row, {
    get (target, key) {
      // target is the target object row
      // 1. Set each key in target to a responsive DEP key->dep
      let dep = getDep(target, key)
      // 2. Collect deP dependencies for this key
      dep.depend()
      return Reflect.get(target, key)  // Target [key]
    },
    set (target, key, value) {
      let dep = getDep(target, key)
      let result = Reflect.set(target, key, value)
      dep.notice()
      return result
    }
  })
}

Copy the code

conclusion

So much for the mini-Vue version of responsiveness, let’s make a quick summary

  • Dep collects and triggers dependencies, and effectWatch executes the dependencies for the first time and communicates with the Dep through currentEffect
  • Reactive essentially returns a Proxy object that stores all reactive objects through targetsMap. The key in each reactive object is a Dep instance that can be collected and triggered by its own dependencies.

Finally, thanks to CXR for bringing mini-Vue