Hi, my name is Mokou and I have been doing vuE3 related work recently, such as source code parsing and the development of Mini-VuE3.
Review the previous chapters, which mainly cover the following.
- New Build tools
vite
The principles and implementation from scratch vue3
Use a new pose- The new API:
reactive
Use and source code analysis - Track collection
track
Implementation and source code analysis - Tracking trigger
trigger
Implementation and source code analysis - Responsive core
effect
与Track, the trigger
Working principle and source code analysis
Ok, the goal of this chapter: build a Vue3 from scratch!
The prerequisite knowledge that must be known is effect and the working principle of track and trigger. For details, please see the public number -> Front-end advanced class, a public number with temperature and no advertising front-end technology.
So let’s just do a quick analysis of what these three functions do
- Track: Collects dependencies and stores them
targetMap
- Trigger: indicates the use of the trigger dependency
targetMap
- Effect: Side effects
This chapter source see Uuz urgently need star to maintain a livelihood.
Contents of the first two chapters:
- Vue3. X Series (Serial 1)
- Vue3. X Series (Serial 2)
Hands touch hands to achieve Vue3
In the first place. We have two global variables that store and locate trace dependencies, which are repositories for track and trigger.
let targetMap = new WeakMap(a);let activeEffect;
Copy the code
So the first method that needs to be designed is track. Remember how track is called in VUe3?
track(obj, 'get'.'x');
Copy the code
Track will find out if obj.x is tracked. If not, put obj.x in targetMap, use obj.x as the key of the map, and activeEffect as the value of the map.
Aside from value exception handling and the like, track does one thing: plug activeEffect into targetMap;
function track(target, key) {
// First find if obj is tracked
let depsMap = targetMap.get(target);
if(! depsMap) {// If not, add one
targetMap.set(target, (depsMap = new Map()));
}
// Then find if obj.x is tracked
let dep = depsMap.get(key);
if(! dep) {// If not, add one
depsMap.set(key, (dep = new Set()));
}
// If no activeEffect is added, add one
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
}
}
Copy the code
Then you write a trigger. Remember how trigger is called in Vue?
trigger(obj, 'set'.'x')
Copy the code
Trigger will only go to targetMap to find the obj.x tracking task, and if it finds one, it will undo it and execute the task.
That is: regardless of the value exception, trigger does only one thing: value from targetMap and then call the function value.
function trigger(target, key) {
// Find the trace item
const depsMap = targetMap.get(target);
// Do nothing until you find it
if(! depsMap)return;
/ / to heavy
const effects = new Set()
depsMap.get(key).forEach(e= > effects.add(e))
/ / execution
effects.forEach(e= > e())
}
Copy the code
Finally, effect. Remember how the worker’s API is called in VUe3?
effect((a)= > {
console.log('run cb')})Copy the code
Effect receives a callback function, which is then sent to Track. So we can do effect this way
- Define an internal function
_effect
And execute. - Returns a closure
Internal _effect also does two things
- Assign itself to
activeEffect
- perform
effect
The callback function
Good code is coming.
function effect(fn) {
// Define an internal _effect
const _effect = function(. args) {
// Assign itself to activeEffect during execution
activeEffect = _effect;
// Perform the callback
returnfn(... args); }; _effect();// Return the closure
return _effect;
}
Copy the code
Now that all the prerequisites are complete, it’s time to start working on a Reactive, object responsive API. Remember how to use Reactive in VUE3?
<template>
<button @click="appendName">{{author.name}}</button>
</template>Setup () {const author = reactive({name: 'mokou',}) const appendName = () => author.name += 'excellent '; return { author, appendName }; }Copy the code
With the excellent code above, it is easy to implement vuE3’s responsive operation. By reviewing the previous chapters, we know that Reactive is implemented by Proxy for data.
This allows us to invoke track and trigger via Proxy, hijacking getters and setters for reactive design
export function reactive(target) {
// Proxy data
return new Proxy(target, {
get(target, prop) {
// Perform tracing
track(target, prop);
return Reflect.get(target, prop);
},
set(target, prop, newVal) {
Reflect.set(target, prop, newVal);
/ / triggering effect
trigger(target, prop);
return true; }})}Copy the code
All right. All set, let’s mount our fake Vue3
export function mount(instance, el) {
effect(function() {
instance.$data && update(el, instance);
})
instance.$data = instance.setup();
update(el, instance);
}
function update(el, instance) {
el.innerHTML = instance.render()
}
Copy the code
Write a demo with mini-vue3
Test it out. Refer to vue3. Define setup and render.
const App = {
$data: null,
setup () {
let count = reactive({ num: 0 })
setInterval((a)= > {
count.num += 1;
}, 1000);
return {
count
};
},
render() {
return `<button>The ${this.$data.count.num}</button>`
}
}
mount(App, document.body)
Copy the code
Let’s do it. It’s good code. After each setInterval, the page is rewritten to refresh the count.num data.
Source code see uuz, PS: July 23 the source code has support for JSX.
With 50+ lines of code, vuE3’s responsiveness is easily implemented. But is that the end of it?
There are also the following questions
Proxy
You must pass in objectsrender
Functions andh
Function and correct (Vue3 h function is now 2 not beforecreateElement
A)- Recursion of the virtual DOM
- Don’t say anymore
- -!
I won’t listen.
ref
One drawback to using Reactive is that proxies can only Proxy objects, not underlying types.
Uncaught TypeError: Cannot create Proxy with a non-object as target or handler if you call this code new Proxy(0, {}), Uncaught TypeError: Cannot create Proxy with a non-object as target or handler
So, for the base type of proxy. We need a new approach, and in VUe3, the new API for the base type is REF
<button >{{count}}</button> export default { setup() { const count = ref(0); return { count }; }}Copy the code
Implementing a REF is pretty simple: you can do it using the getters that come with the JS object
Here’s an example:
let v = 0;
let ref = {
get value() {
console.log('get')
return v;
},
set value(val) {
console.log('set', val)
v= val;
}
}
ref.value; / / print the get
ref.value = 3; / / print the set
Copy the code
The ref can be easily implemented through the track and trigger implemented in the previous chapters
Code done directly on
function ref(target) {
let value = target
const obj = {
get value() {
track(obj, 'value');
return value;
},
set value(newVal) {
if(newVal ! == value) { value = newVal; trigger(obj,'value'); }}}return obj;
}
Copy the code
computed
So how do you implement computed?
First: see vuE3 for computed use
let sum = computed((a)= > {
return count.num + num.value + '! '
})
Copy the code
Blind guessing leads to an idea that can be implemented by modifying Effect so that the run method is not executed at the moment effect is called. So we can add a lazy argument.
function effect(fn, options = {}) {
const _effect = function(. args) {
activeEffect = _effect;
returnfn(... args); };// Add this code
if(! options.lazy) { _effect(); }return _effect;
}
Copy the code
So computed can be written this way
- Internal implementation
effect(fn, {lazy: true})
ensurecomputed
Execute without triggering a callback. - Pass object
getter
Properties,computed
The callback is executed when it is used. - through
dirty
Prevent memory overflow.
Good code coming up:
function computed(fn) {
let dirty = true;
let value;
let _computed;
const runner = effect(fn, {
lazy: true
});
_computed = {
get value() {
if (dirty) {
value = runner();
dirty = false;
}
returnvalue; }}return _computed;
}
Copy the code
So the question is how do you reset dirty when it’s set to false after the first execution?
Vue3’s solution at this point is to add a scheduler to Effect to handle side effects.
function effect(fn, options = {}) {
const _effect = function(. args) {
activeEffect = _effect;
returnfn(... args); };if(! options.lazy) { _effect(); }// Add this line
_effect.options = options;
return _effect;
}
Copy the code
Now that we have a Scheduler, we need to change the trigger to handle the new scheduler.
function trigger(target, key) {
const depsMap = targetMap.get(target);
if(! depsMap)return;
const effects = new Set()
depsMap.get(key).forEach(e= > effects.add(e))
// Change this line
effects.forEach(e= > scheduleRun(e))
}
// Add a method
function scheduleRun(effect) {
if(effect.options.scheduler ! = =void 0) {
effect.options.scheduler(effect);
} else{ effect(); }}Copy the code
Then, by combining the above code, computed is done
function computed(fn) {
let dirty = true;
let value;
let _computed;
const runner = effect(fn, {
lazy: true.scheduler: (e) = > {
if(! dirty) { dirty =true;
trigger(_computed, 'value'); }}}); _computed = { get value() {if (dirty) {
value = runner();
dirty = false;
}
track(_computed, 'value');
returnvalue; }}return _computed;
}
Copy the code
conclusion
- The core of Reactive is
track
+trigger
+Proxy
- Ref is owned by the object
getter
和setter
Cooperate withtrack
+trigger
Implementation of the - Computed is actually a problem in
effect
On the basis of improvement
Next chapter: How does VUE3 combine with JSX?
The last
Original is not easy, give a three even comfort next brother.
- See uuz for the source code
- This article is from github.com/zhongmeizhi…
- Welcome to pay attention to the public number “front-end advanced class” seriously learn the front end, step up together. reply
The whole stack
或Vue
I got a nice gift for you