When using vuE3 responsiveness, we usually use reactive or ref directly in setup, such as the following

<script setup>
  let user = reactive({
    age: 1
  })  
  let tree = ref(3)
</script>
Copy the code

The user and tree then form a reactive relationship with the UI view. When we change the data, the view changes with it.

However, vue3’s response can be separated from the view, and there is an effect function hidden in the above method. In this article, REACTIVE, REF, and Effect will be introduced first, followed by Setup.

Let’s go back to the original response.

let a = 1
let b = a + 1
a = 2
// If we want b to change as a changes, we can reassign it
b = a + 1
Copy the code

Going one step further, we can encapsulate the assignment as a function that executes it every time a changes. That’s one way to do it.

let a = 1
let b
function update() {
  b = a + 1
}
a = 2
update()
Copy the code

What does vue3’s Reactivity do?

let a = ref(1)
let b
effect(() = > {
  b = a + 1
})
a.value = 2
console.log(b) / / 3
Copy the code

It uses Reactive or REF to act as a proxy for the original data, and uses Effect to collect dependencies. When the original data changes, the dependency is triggered, that is, the effect function is automatically executed.

Explore the implementation of Reactive in VUe3

import { track, trigger } from "./effect"
function reactive(raw){
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)
​
      // Rely on collection
      track(target, key)
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
​
      // Trigger dependencies
      trigger(target, key)
      return res
    }
  })
}
Copy the code

Let’s separate out the effect-dependent methods and start with the Reactive methods.

Vue3 reactive actually returns a Proxy. When it gets, it uses reflect. get to return the corresponding value (equivalent to target[key]), and then collects dependencies through track. When it sets, it uses reflect. set to set the corresponding value (equivalent to target[key] = newValue), and then triggers the dependency via trigger.

However, it only returns a Proxy Proxy in this step, and the process of collecting and triggering dependencies is not performed.

Next, let’s look at the Effect method.

let activeEffect;
class ReactiveEffect {
  constructor(fn) {
    this._fn = fn
  }
  run() {
    activeEffect = this
    this._fn()
  }
}
​
function effect(fn) {
  const _effect = new ReactiveEffect(fn)
  _effect.run()
}
​
const targetMap = new Map(a)function track(target, key) {
  let depsMap = targetMap.get(target)
  if(! depsMap) { depsMap =new Map()
    targetMap.set(target, depsMap)
  }
​
  let dep = depsMap.get(key)
  if(! dep) { dep =new Set()
    depsMap.set(key, dep)
  }
​
  dep.add(activeEffect)
}
​
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  const dep = depsMap.get(key)
  dep.forEach(effect= > effect.run())
}
Copy the code

Here we use an example to illustrate

let son = reactive({
  age: 25
})
let dad 
effect(() = > {
  dad = son.age + 25
})
son.age = 26
console.log(dad) / / 51
Copy the code

Effect first receives a function. Then vue3 uses an object-oriented idea, creating a new object in effect, which stores the fn passed in, mounting this point to the global variable activeEffect in the run method, and then implementing the fn passed in. When dad = son.age + 25 is executed, reactive returns the get method in proxy, which executes effect track to collect dependencies.

What about collecting dependencies?

First, the effect file has a public variable, targetMap, which is the Map data type (allowing objects as keys).

Now track. In this case, the target variable it accepts, the key of targetMap, is {age: 25}, and this target corresponds to a Map data type (depsMap).

DepsMap takes the original object’s key, age, as its own key, corresponding to a Set data type (with unique elements), and stores the dependent object _effect created in effect.

The resulting data structure looks something like this:

targetMap: {
  // Key is the object and value is depsMap
  {age: 25}, {// Key is the key in the object and value is dep
    age: [... store dependencies here]}}Copy the code

Continuing with the example, the logic in effect has run its course, so son.age = 26. This will trigger the set method in the proxy, execute trigger, find the depsMap deP based on the target and key, and iterate over the RUN method in the DEP. Dad = son.age + 25.

Explore the implementation of ref in VUe3

Reactive is used to delegate complex data, while refs are used for simple data such as numbers, strings, and Booleans. When we collect dependencies, we don’t need a complex targetMap, just a Set data structure.

Let’s first extract the last step in track and trigger

function track(target, key) {...// dep.add(activeEffect)
  trackEffects(dep)
}
function trackEffects(dep) {
  dep.add(activeEffect)
}
​
function trigger(target, key) {...// dep.forEach(effect => effect.run())
}
function triggerEffects(dep) {
  dep.forEach(effect= > effect.run())
}
Copy the code

Then look at a simple implementation of ref

function ref(raw) {
  return new RefImpl(raw)
}
class RefImpl {
  constructor(raw) {
    this._value = raw
    this.dep = new Set()}get value() {
    trackEffects(this.dep)
    return this._value
  }
  set value(newValue) {
    this._value = newValue
    triggerEffects(this.dep)
  }
}
Copy the code

Let’s do another simple example

const apple = ref(1)
let banana
effect(() = > {
  banana = apple.value + 2
})
apple.value = 2
console.log(banana) / / 4
Copy the code

Const apple = ref(1) creates a RefImpl instance inside the ref function, storing the value passed in as a private variable _value, and creating a deP that stores the dependency.

When get value is collected, it depends on the DEP and returns the value. Set value to set a new value and trigger a dependency. Again, only the instance object is returned, not the get and set methods.

Reactive creates a ReactiveEffect object and executes the passed method. Reactive creates a ReactiveEffect object and executes the passed method.

The RefImpl get method is triggered when an apple.value is read. In this case, the trackEffects method stores the newly created ReactiveEffect instance object in RefImpl’s own DEP instead of the public targetMap variable stored in the effect file.

If apple.value = 2, trigger RefImpl’s set method, remove the deP from the RefImpl, and iterate through the run method. Banana = apple.value + 2.

Study reference: Cui Xiaorui Mini-vue