SoFunction
Updated on 2025-04-12

Master the principle of vue-next responsiveness in 15 minutes

Write in front

The latest source code of vue-next has been released. Although it is the pre-alpha version, it is actually a better time to read the source code. In vue, of course, the more important thing is its responsive system. In previous versions, several articles have introduced its responsive principles and implementations, so I will not go into details here. In vue-next, its implementation principle is the same as before, that is, it is hijacked through observer patterns and data, except that its implementation method has been changed.

For articles that analyse principles, I personally prefer articles in the "novel" style, that is, do not excerpt a lot of code, nor do I explain some profound principles and concepts. When I first came into contact with react, I still remember an article using jquery to introduce react, which ranged from simple to traditional and comprehensive. The knowledge points explained behind it helped me a lot in my later learning react.

Therefore, in this article, I also plan to write some of my experiences and experiences in using my recent free time to read the source code of the vue-next responsive module in this style. It is a way to attract attention and realize a minimalist responsive system.

If there is any error, I hope to correct it.

Preparation knowledge

Whether reading this article or reading the source code of vue-next responsive module, there are two necessary knowledge points:

  • Proxy: New proxy built-in tool class in es6
  • Reflect: New reflection tool class in es6

Due to the limited space, I will not elaborate on the uses and usage methods of these two categories here. I recommend three articles that I think are good, for reference only:

  • ES6 Proxies in Depth
  • ES6 Proxy Traps in Depth 
  • ES6 Reflection in Depth

interface

For RFC of vue-next responsive system, please refer to this. Although it has been a while since then, by reading the source code, you can find some shadows.

The effect we want to achieve is shown in the following code:

// Implement two methods reactive and effect
const state = reactive({
  count: 0
})

effect(() => {
  ('count: ', )
})

++ // Enter count: 1

It can be found that the dependency collection stage we are familiar with (also also the subscription process of the observer pattern) is carried out in effect. The preparation work of the dependency collection (i.e., data hijacking logic) is carried out in reactive, and the logic of triggering responses for data changes is performed when the ++ code is executed later (also also the publishing process of the observer pattern). After that, the callback function passed into the effect and the count: 1 will be executed.

Types and public variables

Since vue-next is rewritten with ts, here I also use ts to implement this minimalist version of the responsive system. The types and public variables that are mainly involved are as follows:

type Effect = Function;
type EffectMap = Map<string, Effect[]>;

let currentEffect: Effect;
const effectMap: EffectMap = new Map();
  • CurrentEffect: Used to store the effect that is currently collecting dependencies
  • effectMap: represents the effect array corresponding to each key of the target object that depends on it, and can also be understood as a subscriber dictionary in the observer pattern.

Using Proxy to implement data hijacking

In previous versions, vue used setters and getters in hijacking data objects, while vue-next passed Proxy. As we all know, the data hijacking implemented has certain limitations, and Proxy will be much more powerful.

First, in the back of our minds, imagine how to use Proxy to implement data hijacking? It's very simple, the general structure is as follows:

export function reactive(obj) {
 const proxied = new Proxy(obj, handlers);

 return proxied;
}

The handlers here are the logic that declare how to handle each trap, such as:

const handlers = {
  get: function(target, key, receiver) {
    ...
  },
  set: function(target, key, value, receiver) {
    ...
  },
  deleteProperty(target, key) {
    ...
  }
  // ...and other traps }

Since this is a minimalist version of the implementation, we can only implement two traps, get and set, corresponding to the logic of dependency collection and triggering responses.

Dependency collection

For implementations that depend on collection, since they are minimalist versions, the implementation prerequisites are as follows:

  • Nesting objects without considering nesting
  • Don't consider the collection type
  • No basic type considered
  • No consideration of handling proxy objects

Haha, after basically these four points are excluded, this dependency collection function will be very light and thin, as follows:

function(target, key: string, receiver) {
    // Only dependency collection is performed inside a certain effect    if (currentEffect) {
     if ((key)) {
      const effects = (key);
      if ((currentEffect) === -1) {
       (currentEffect);
      }
     } else {
      (key, [currentEffect]);
     }
    }

   return (target, key, receiver);
}

The implementation logic is very simple. In fact, it is the implementation logic of registering subscribers in the observer mode. It is worth noting that we entrust the assignment logic of target to Reflect here. Although target[key] can also work, using Reflect is a more recommended way.

Trigger response

The logic for triggering the response is relatively simple. In fact, it is the logic for publishing events in the corresponding observer mode, as follows:

function(target, key: string, value, receiver) {
    const result = (target, key, value, receiver);
    
    if ((key)) {
     (key).forEach(effect => effect());
    }

    return result;
}

Similarly, here Reflect is used to assign a target because it will return a boolean value to represent whether it is successful, and set trap also needs to represent a value with the same meaning.

Initialize the proxy object through the reactive method

After implementing the proxy logic for data hijacking, we only need to return an instance of the proxy object in the reactive method. Do you still remember the general code framework that came to our mind before implementing it?

as follows:

export function reactive(obj: any) {
 const proxied = new Proxy(obj, {
  get: function(target, key: string, receiver) {
   if (currentEffect) {
    if ((key)) {
     const effects = (key);
     if ((currentEffect) === -1) {
      (currentEffect);
     }
    } else {
     (key, [currentEffect]);
    }
   }

   return (target, key, receiver);
  },
  set: function(target, key: string, value, receiver) {
   const result = (target, key, value, receiver);

   if ((key)) {
    (key).forEach(effect => effect());
   }

   return result;
  }
 });

 return proxied;
}

Depend on collection preparation

As mentioned above, we do the work of dependency collection conditionally, that is, we will only collect in one effect, and in other cases we will not collect dependency in the value logic. Therefore, the effect method officially exists to achieve this, as follows:

export function effect(fn: Function) {
 const effected = function() {
  fn();
 };

 currentEffect = effected;
 effected();
 currentEffect = undefined;

 return effected;
}

The reason why it is so simple is that we are a minimalist version here, and we don't need to consider factors such as readOnly, exceptions, and collection timing. It can be found that the incoming callback function is wrapped in another method, and then temporarily store this method with the currentEffect variable, and then try to run it. After the effect is completed, put currentEffect empty, so that the purpose of only dependent collection under effect can be achieved.

Running effect

I simply wrote a counter demo on codepen, the link is as follows:
/littlelyon1/pen/mddVPgo

Written at the end

Although this minimalist responsive system can be used, there are many unconsidered factors. In fact, they are the prerequisites that we overlooked in the above. Here are some more examples and the solutions in the source code are given:

  • Processing of basic data types: The basic data type can be encapsulated into a ref object whose value points to the value of the basic data type.
  • Nested objects: just perform the proxy process recursively
  • Collection Objects: Write special trap processing logic
  • Proxy instances: cache these proxy instances and return them directly next time you encounter them.

But I still recommend you to read the source code directly, because you will find that the source code will use more complex data structures and processes based on this minimalist version to control the process of relying on collection and triggering responses, and at the same time, there are more detailed considerations in various special circumstances.

In addition, this is just a simple implementation of vue-next responsive system, such as other functional modules, such as instructions, template analysis, vdom, etc. I am also planning to use my recent free time to check it out. If you have time, I will sort it out recently and share it with you.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.