First, the following code:

const obj = { age: 1 } // Define an object
let age = obj.age // Assign the age attribute of the object to a variable
obj.age++ // The age property of the object has changed
console.log(`age=${age}`, obj); Age = 1 {age: 2}
Copy the code

When the age property of OBj changes and the age variable changes with it, it is usually necessary to define a function to give the change logic and execute the function manually each time the change occurs. The following uses the responsive design in VUE3 as a reference to implement an automatic response method.

Create a Demo

In Vue3, the data response module is split out. Now you can create a Node project and install the NPM or YARN package @vue/ reActivity, which has two key functions, Reactive and Effect. Create a responsive object and listen when data changes:

Using the functions introduced in the package, we modified the above example to meet the reactive requirement that externally defined variables change along with the object, printing two values that match:

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

const obj = reactive({ age: 1 }) // This creates a responsive object

let _age

effect(() = > { // Listen to the response when the object changes
    _age = obj.age
    console.log(`I'm ${_age} years old`);
})

obj.age++ // Manipulate the object, and the data responds accordingly

console.log(_age, obj); // Result: 2 {age: 2}
Copy the code

Simulations implement basic reacelasticity

/** * 1. Collect dependencies. 2
class Dep {
    constructor(val) {
        this.effects = new Set(a)this._val = val
    }
    get value() {
        return this._val
    }
    set value(newVal) {
        this._val = newVal
    }
    depend() {
        // Rely on collecting...
    }
    notify() {
        // Trigger notification...}}function effectWatch(effect) {}
Copy the code

Starting with the results, let’s look at a test case, assuming we start with the basic data types:

// Test
const hero = new Dep(1) // Pass in a value
let lv
effectWatch(() = > { // Execute the function in response to changes in the object
    lv = hero.value
    console.log('Grade promotion:${lv}`); // Expected result.. Level improvement :2
})
setTimeout(() = > {
    hero.value++
}, 1000);
Copy the code

Create a hero upgrade example from the above example, the expected result should be to print a level increase :2, and start refining the function:

class Dep {
    effects: any
    val: any
    constructor(val: any) {
        this.effects = new Set(a)this.val = val
    }
    get value() {
        this.depend() // Start collecting dependencies in get
        return this.val
    }
    set value(newVal) {
        if(newVal ! = =this.val) {
            this.val = newVal
            this.notify() // Trigger notification in set}}depend() { // Collect dependencies
        if (Dep.currentEffect) {
            this.effects.add(Dep.currentEffect)
            Dep.currentEffect = null}}notify() { // Perform dependencies
        this.effects.forEach((effect: Function) = > effect())
    }
    static currentEffect: any
    static trigger(e: Function) {
        Dep.currentEffect = e
    }
}

function effectWatch(effect: Function) :void {
    effect() // The effect function mimics vue3 and is executed once
    Dep.trigger(effect) // Commit to the dependency collector
}
Copy the code

There’s been a bit of a hiccup. Collect dependency definition intermediate variable currentEffect, keep reporting errors (either the variable is defined outside the class), search for the class static attribute and find that it is still a proposal? Or is my node10 version too low? Yarn Global add typescript + YARN Global add TS-node run TSC –init and modify the generated JSON to point to ES6: “target”: “Es2015”, using the TS-Node command, the code structure is the same, mainly improve the get set to collect and trigger dependencies, then run the test case, success output:

// TestRun the Test code above..... Grade improvement:1Grade improvement:2
Copy the code

Now that we have implemented responsive code for basic data types, objects that manipulate more complex data require knowledge of the new ES6 feature proxy.

Responsive object

Reactive implements basic data types, and then implements object processing as in the previous Vue3 example, except that the two imported functions are implemented by themselves:

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

const obj = reactive({ age: 1 }) // create a responsive object. Omit the obj. Age++// Manipulate the object, and the data responds accordingly
console.log(_age, obj); // Result: 2 {age: 2}
Copy the code

Now all that is needed is the implementation of reactive. Continue with the code above:

class Dep {... }function effectWatch(effect: Function) :void {... }// The node environment is directly exported from es6
export const effect = effectWatch

export function reactive(target: any) {
    return new Proxy(target, {
        get(target: any, key: string | symbol) {
            return Reflect.get(target, key)
        },
        set(target: any, key: string | symbol, value: any) {
            return Reflect.set(target, key, value)
        }
    })
}
Copy the code

Proxy helps parse objects, and in this case performs a recursive effect. The next step is to bind the Dep instance in Proxy and trigger dependency collection. Let’s define a function access instance:

const targetMaps = new Map(a)// The data structure is Map, because with objects the key names are formatted as strings
function getDep(target: any, key: string | symbol) {
    const deps = targetMaps.get(target) || new Map()
    targetMaps.set(target, deps)
    const dep = deps.get(key) || new Dep()
    deps.set(key, dep)
    return dep
}
Copy the code

You can then use the getDep method in Reactive:

export function reactive(target: any) {
    return new Proxy(target, {
        get(target: any, key: string | symbol) {
            const dep = getDep(target, key) // Get the attribute corresponding to the Dep instance
            dep.depend() // Triggers a collection dependency
            return Reflect.get(target, key)
        },
        set(target: any, key: string | symbol, value: any) {
            const dep = getDep(target, key)
            const result = Reflect.set(target, key, value)
            result && dep.notify() // Notify when data changes
            return result
        }
    })
}
Copy the code

Run the test case changed to import your own package above to see if the results match the Vue3 example.

Attached is the complete code for reactivity.ts

class Dep {
    effects: any
    val: any
    constructor(val: any = ' ') {
        this.effects = new Set(a)this.val = val
    }
    get value() {
        this.depend() // Start collecting dependencies in get
        return this.val
    }
    set value(newVal) {
        if(newVal ! = =this.val) {
            this.val = newVal
            this.notify() // Trigger notification in set}}depend() {
        if (Dep.currentEffect) {
            this.effects.add(Dep.currentEffect)
            Dep.currentEffect = null}}notify() {
        this.effects.forEach((effect: Function) = > effect())
    }
    static currentEffect: any
    static trigger(e: Function) {
        Dep.currentEffect = e
    }
}

function effectWatch(effect: Function) :void {
    effect() // The effect function mimics vue3 and is executed once
    Dep.trigger(effect)
}

export const effect = effectWatch

const targetMaps = new Map(a)function getDep(target: any, key: string | symbol) {
    const deps = targetMaps.get(target) || new Map()
    targetMaps.set(target, deps)
    const dep = deps.get(key) || new Dep()
    deps.set(key, dep)
    return dep
}

export function reactive(target: any) {
    return new Proxy(target, {
        get(target: any, key: string | symbol) {
            const dep = getDep(target, key)
            dep.depend()
            return Reflect.get(target, key)
        },
        set(target: any, key: string | symbol, value: any) {
            const dep = getDep(target, key)
            const result = Reflect.set(target, key, value)
            result && dep.notify()
            return result
        }
    })
}
Copy the code