A series of

  1. Implement a simple VDOM engine from scratch
  2. Implementing a responsive state Management from zero
  3. Implement a mini Vue framework from scratch

concept

Simply put, state reactivity means that some specified action is automatically performed when the state of the program changes, such as the value of a variable.

This feature is divided into two main parts:

  1. It notifies a variable when it changes.
  2. Collect the dependent functions of a variable, that is, which functions are interested in changes to the variable, collect them, and notify them when the variable changes.

Dep Class

getter/setter

Since there is no such thing as a variable “assignment hook,” we can’t directly listen for changes to variables, so we need to turn variables into objects first and use getters and setters instead of reading and assigning.

class Dep {
    constructor(value) {
        this._value = value;
    }
    get value() {
        return this._value;
    }
    set value(val) {
        this._value = val; }}Copy the code
  • DepIs a reactive object that currently provides only two “hooks” (getters and setters) that allow us to do certain things when reading and assigning values.

notify

Now let’s implement the first part of the function, which is to notify when the value of a variable changes.

class Dep {
    constructor() {
        this.subscribers = [];
    }
    notify() {
        this.subscribers.forEach(fn= > fn());
    }
    set value(val) {
        this._value = val;
        this.notify; }}Copy the code
  • subscribersIt holds the dependent functions of the current variable.
  • notifyNotifies dependent functions to fire in setter hooksnotifyExecute the dependent functions.

depend

Then there is the work of collecting dependent functions. Simply collect in the getter “hook” of value.

The only problem is where the depend function func comes from. This.depend () is called in the get value, but the getter doesn’t pass arguments either.

class Dep {
    constructor() {
        this.subscribers = new Set(a); }depend() {
        this.subscriber.add(func);
    }
    get value() {
        this.depend();
        return this._value; }}Copy the code

This is tricky. Before get Value is executed, the flow is inside a dependent function funcA, which we store in a global variable and then obtain funcA by reading that global variable on Depend.

const count = new Dep(1);

let activeUpdate = null;

function update() {
    activeUpdate = update;
    console.log(count.value);
}

update();
activeUpdate = null;

setTimeout(() = > (count.value = 2), 2000);
Copy the code
  • activeUpdateHolds the global variables of the current dependent function.
  • update, the variablecountA dependent function of

But we can’t write activeUpdate = update in every dependent function; .

The above code needs to be refactored to use an Autorun function to complete the dependency collection function, so that the dependency function itself does not need to modify the logic.

const count = new Dep(1);

let activeUpdate = null;
function autorun(update) {
    activeUpdate = update;
    update();
    activeUpdate = null;
}

function update() {
    console.log(count.value);
}
autorun(update);
Copy the code

That’s about it. We’ve implemented listening on a raw value variable, but when we use Vue, we’re listening on all the properties of a data object.

For Object properties, JS provides an Object.defineProperty API. You can change this property directly to getters and setters.

function observe(obj) {
    Object.keys(obj).forEach(key= > {
        let internalValue = obj[key];
        const dep = new Dep();
        Object.defineProperty(obj, key, {
            get() {
                dep.depend();
                return internalValue;
            },
            set(newVal){ internalValue = newVal; dep.notify(); }}); }); }class Dep {
    constructor() {
        this.subscribers = new Set(a); }depend() {
        activeUpdate && this.subscribers.add(activeUpdate);
    }
    notify() {
        this.subscribers.forEach(func= >func()); }}Copy the code
  • observeThe function makes all attributes of an object reactive.
  • DepleavesdependnotifyTwo functions.

The complete code

The complete code

PS. If you have any questions about the autorun function in the complete code, you can take a look here.