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