A series of
- Implement a simple VDOM engine from scratch
- Implementing a responsive state Management from zero
- 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:
- It notifies a variable when it changes.
- 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
Dep
Is 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
subscribers
It holds the dependent functions of the current variable.notify
Notifies dependent functions to fire in setter hooksnotify
Execute 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
activeUpdate
Holds the global variables of the current dependent function.update
, the variablecount
A 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
observe
The function makes all attributes of an object reactive.Dep
leavesdepend
和notify
Two 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.