Let’s start with two global constants and one variable.
- TargetMap [new WeakMap] : When track(), trigger(), hit the intermediate correlation container
- EffectStack [Array] : As the name suggests, is the container that stores effects
- ActiveEffect: Can be used as a temporary variable when track() is used as a collection object for Target
1, reactive (target)
Reactive is implemented by reactive (BB)
- Basically, new Proxy() overrides getter and setter methods to implement the logic you need
- Overwrite getter to implement track collection effect
- Override setter reality trigger trigger effect
2, the effect (fn, options)
Effect is the most important part of the track() process and is the only place where activeEffect is provided
3, the computed (getterOrOptions)
The above flow chart shows that the essence of computed is effect.
- 1. Get an anonymous function getter by processing the getterOrOptions parameter
- 2. Take the getter as the effect() argument to get a delayed runner function
- 3. Wrap runner into a ref object and return it.
4, watch (source, cb, options)
Compared to computed, the implementation logic of Watch is relatively complex. This is just a rough implementation. Through the analysis of the source code, it is found that the general principle of Watch is also based on the implementation of Effect.
- Const getter = ()=> source; const getter = ()=> source;
- 2. Get a delayed runner function by effect
- 3. First run runner to complete track()
- 4. Return an anonymous function to stop listening
5. Simple implementation
Key note: the following code (omitted a lot of logic) for the principle of understanding, specific implementation please move to VUE-next source code.
5.1, reactive
Based on the analysis and understanding of reactive(Target), the basic implementation of Reactive is as follows:
function reactive(obj) {
return new Proxy(obj, {
get: function(target, key, receiver) {
// getter
track(target, key); // Collect the effect function
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
// setter
let oldValue = target[key]; / / the original value
if (oldValue === value) {
return;
}
let res = Reflect.set(target, key, value, receiver);
trigger(target, key); // Trigger the effect function
returnres; }}); }Copy the code
If you don’t know About Reflect, you won’t do it here. Track and trigger functions are then implemented
/ /...
const targetMap = new WeakMap(a);const effectStack = [];
let activeEffect = null;
function track(target, key) {
if(! activeEffect) {return;
}
/ / collection
let depsMap = targetMap.get(target);
if(! depsMap) { targetMap.set(target, (depsMap =new Map()));
}
let dep = depsMap.get(key);
if(! dep) { depsMap.set(key, (dep =new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
function trigger(target, key) {
/ / triggers
const depsMap = targetMap.get(target);
if(! depsMap) {return;
}
let dep = depsMap.get(key);
let effects = new Set(a); dep && dep.forEach((effect) = > effects.add(effect));
/ / execution
effects.forEach((effect) = > {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else{ effect(); }}); }Copy the code
This is the general code of the reactive implementation. It’s not complete, but it’s understandable. Students who want to know more can go directly to vue-next source code.
5.2, the effect
Next comes the implementation of Effect (FN,options). The code is as follows:
const effectStack = [];
let activeEffect = null;
/ /...
function effect(fn, options = {}) {
let effect = createEffect(fn, options);
if(! options.lazy) { effect(); }return effect;
}
let uid = 0;
function createEffect(fn, options) {
const effect = () = > {
if(! effectStack.includes(effect)) {try {
effectStack.push(effect);
activeEffect = effect;
// The collection is performed by default
return fn();
} finally {
// The processing is complete
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1]; }}}; effect.raw = fn; effect.id = uid++; effect.options = options;/ /...
return effect;
}
Copy the code
Effect (fn,options) returns the result of fn(). And if you put reactive and effect together you can see how dependent they are. Proxy uses getter to collect activeEffects and setter to trigger activeeffects. Two seemingly independent methods are connected by the global variable activeEffect.
5.3, the computed
With reactive and effect implemented, computations are much simpler. Implementation code for computed:
/ /...
function computed(fn) {
let runner = effect(fn, {
lazy: true});return {
get value() {
// Perform the collection
return runner();
},
set value(value) {}}; }/ / use
let data = reactive({ count : 0 })
let text = computed(() = > {
return `count:${data.count}`;
});
console.log(text.value) // count:0
Copy the code
Yes, that’s right. The implementation of computed is that simple. Compared with computed and watch, the implementation is much more complicated.
5.3, watch
// This is just a simple implementation of watch for understanding
function watch(source, cb) {
let getter = () = > {};
if (isFunction(source)) {
getter = source;
}
// Collect information
let runner = effect(getter, {
lazy: true.scheduler: () = > {
// Perform the callback
let value = runner();
if(value ! == oldValue) { cb(oldValue, value); } oldValue = value; }});// Perform collection for the first time
let oldValue = runner();
// Stop listening
return () = >{
stop(runner)
/ /...}}/ / use
let stopWatcher = watch(
() = > data.count,
(oldValue, value) = > {
/ / execution
console.log("========watch========", oldValue, value); });// You can stop watch
// stopWatcher()
data.count = 100;
// ========watch======== 0 100
Copy the code
5.4 complete code
// reactive
function reactive(obj) {
return new Proxy(obj, {
get: function(target, key, receiver) {
track(target, key);
// getter
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
let oldValue = target[key]; / / the original value
if (oldValue === value) {
return;
}
// setter
let res = Reflect.set(target, key, value, receiver);
trigger(target, key);
returnres; }}); }const targetMap = new WeakMap(a);const effectStack = [];
let activeEffect = null;
// track
function track(target, key) {
if(! activeEffect) {return;
}
/ / collection
let depsMap = targetMap.get(target);
if(! depsMap) { targetMap.set(target, (depsMap =new Map()));
}
let dep = depsMap.get(key);
if(! dep) { depsMap.set(key, (dep =new Set()));
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
// trigger
function trigger(target, key) {
/ / triggers
const depsMap = targetMap.get(target);
if(! depsMap) {return;
}
let dep = depsMap.get(key);
let effects = new Set(a); dep && dep.forEach((effect) = > effects.add(effect));
/ / execution
effects.forEach((effect) = > {
if (effect.options.scheduler) {
effect.options.scheduler(effect);
} else{ effect(); }}); }// effect
function effect(fn, options = {}) {
let effect = createEffect(fn, options);
if(! options.lazy) {// Execute the phone
effect();
}
return effect;
}
let uid = 0;
function createEffect(fn, options) {
const effect = () = > {
if(! effectStack.includes(effect)) {try {
effectStack.push(effect);
activeEffect = effect;
// Perform collection for the first time
return fn();
} finally {
// The processing is complete
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1]; }}}; effect.raw = fn; effect.id = uid++; effect.options = options;return effect;
}
// computed
function computed(fn) {
let runner = effect(fn, {
lazy: true});return {
get value() {
// Perform the collection
return runner();
},
set value(value) {}}; }// watch
function watch(source, cb) {
let getter = () = > {};
if (typeof source === "function") {
getter = source;
}
// Collect information
let runner = effect(getter, {
lazy: true.scheduler: () = > {
// Perform the callback
let value = runner();
if(value ! == oldValue) { cb(oldValue, value); } oldValue = value }, });// Perform collection for the first time
let oldValue = runner();
}
Copy the code
6, summary
The following points can be summarized through simple implementation:
- The vue core is the core of rewriting getters and setters of new Proxy() to implement data-driven implementation
- The implementation of computed and watch shows that the actual principle depends on the Effect () factory
- ActiveEffect can be thought of as a transportation hub between New Proxy() and Effec ()
Once again: the above implementation code is only for understanding the implementation principle, detailed implementation please move to VUE-next source code.
If there are mistakes, welcome to correct them