preface
As vue3X is about to be widely used, let’s take a closer look at the data responses and DOM updates for Vue3X
Vue core classes
- Implement the renderer extension
- Template initialization, data update
Create a Vue
- CreateApp creates an app instance
- CreateRenderer renderer, where createApp is the actual create function
- CreateRenderer -> mount The node to save data sources such as data setup passed in by the user
- CreateRenderer -> proxy intercepts data only for data and setup priorities, not for reactive triggering
- In the side effect function are the callbacks that are triggered when the actual data responds.
- In vue3, ast (abstract syntax tree) is used, template => ast => js(render => get virtual DOM)
const Vue = {
createApp(options) {
// Get renderer platform extensibility
const renderer = Vue.createRenderer({
querySelector(selector) { // True platform-specific operations, passed in from the platform, are currently implemented on the Web platform
return document.querySelector(selector);
},
insert(child, parent, anchor) { // Anchor reference node, if not passed, insertBefore equals appendChild
parent.insertBefore(child, anchor || null)}})return renderer.createApp(options);
},
createRenderer({ querySelector, insert }) { // Extend into higher-order functions, platform-specific operation, you can directly write the corresponding renderer directly for multi-platform extension use
// Get the renderer
return {
createApp(options) {
return {
mount(selector) {
// Host element
const parent = querySelector(selector); // Extended platform host fetching functions
this.setupState = {};
this.data = {};
console.log('selector', parent);
// get the render function and compile the result
if(! options.render) {// If there is no render in the option, then fetch
options.render = this.compile(parent.innerHTML);
}
if (options.setup) { // Save the values in setup
this.setupState = options.setup();
}
if (options.data) { // Save the value in data
this.data = options.data();
}
Vue3 uses proxy to listen to data
this.proxy = new Proxy(this, {
get(target, key) { // Proxy target and access key
console.log('target', target);
if (key in target.setupState) { // Setup has a high priority. If the access key is in it, it is returned directly
return target.setupState[key];
} else {
return target.data[key]
}
},
set(target, key, val) {
if (key in target.setupState) { // Setup has a high priority. If the access key is in it, it is updated directly
target.setupState[key] = val
} else {
target.data[key] = val
}
}
})
// The update function is set to the side effect callback. If reactive data is retrieved in options.render, dependencies are automatically collected in Effect, and when the data changes, the callback is executed to re-render the DOM
this.update = effect(() = > {
// Render the DOM and append the host element
const el = options.render.call(this.proxy); // The binding context can get values from data or setup in the instance
parent.innerHTML = ""; // In vue3, it is empty directly
insert(el, parent); // Platform corresponding insert function})},compile(template) { / / return to render
return function render() {
// Describe the view
const h1 = document.createElement("h1");
h1.textContent = this.title;
return h1;
}
}
}
}
}
}
}
Copy the code
Reactive data Response
Reactive implements data response, dependent collection, and is the core mechanism for data response in VUE3. It is easier to understand and more powerful than vuE2. Use Proxy for data listening, without additional processing of array interception,
const isObject = v= > typeof v === "object"&& v ! = =null;
// Listen for data in response
const reactive = function(obj) {
if(! isObject(obj))return obj; // If it is not an object, no proxy is required
return new Proxy(obj, {
get(target, key) {
console.log("get: ", key);
const res = Reflect.get(target, key); // Handle exceptions that can be caught and must return a value
track(target, key); // Dependency collection trigger (subscription)
return isObject(res) ? reactive(res) : res; If the data I'm accessing is an object, then make that object responsive as well, rather than all recursive at first, saving initialization time and moving the response of the deep object to runtime
},
set(target, key, val) {
console.log("set: ", key);
const res = Reflect.set(target, key, val);
trigger(target, key); // Take out the update function that was put in when the dependency was collected and call update dom (publish)
return res;
},
deleteProperty(target, key) {
console.log("delete: ", key);
const res = Reflect.deleteProperty(target, key);
trigger(target, key);
returnres; }})}Copy the code
Effect side effect function
Receives a callback that uses reactive data, invokes it (initialization), and dependencies are collected when reactive data is accessed in the corresponding callback (in Reactive GET).
// Temporarily save the reactive function, that is, fn passed in
const effectStack = [];
// If the side effect function uses reactive data, try to establish a relationship between them
const effect = function(fn) {
// Fn may have exceptions to encapsulate and capture to prevent program freezes
const eff = () = > {
try {
effectStack.push(fn); // Pass in a reactive side effect function. Arrays are used because if effects are nested, the JS stack will run deeper and multiple Fn's will need to be stored
fn(); // Execute the trigger dependency collection process
} finally {
effectStack.pop(); // Unstack after collection
}
}
eff(); / / initialization
return eff;
}
Copy the code
Track relies on collecting
// Store dependency map, use WeakMap, advantage, can use object as key, and weak reference, such as internal object disappear or delete, do not worry about memory leak garbage release
const targetMap = new WeakMap(a);// Rely on collection
const track = function(target, key) {
// When the function is executed, get in proxy will be triggered immediately, and track will be called immediately. This is the critical moment to establish the connection
const eff = effectStack[effectStack.length - 1]; // Get the latest side effect function
if (eff) {
// Get the map of the responsive data object (target is an object)
let depMap = targetMap.get(target);
if(! depMap) {// Create one if it does not exist
depMap = new Map(a); targetMap.set(target, depMap);/ / in
}
// Get the set corresponding to the key.
let deps = depMap.get(key);
if(! deps) {// Create one if it does not exist
deps = new Set(a);// The set structure can be de-duplicated, and the same callback function can be added only once
depMap.set(key, deps);
}
// Establish the relationship between the target key and the EFF. After the relationship is established, it can be retrieved and used where neededdeps.add(eff); }}Copy the code
Trigger relies on the trigger to call the corresponding update function
// Dependency triggers
const trigger = function(target, key) {
// Get the map of target
const depMap = targetMap.get(target);
if (depMap) {
// Get the corresponding set by key
const deps = depMap.get(key);
if (deps) { // If so, iterate over all side effects (update functions with responsive data)
console.log('depends on firing target:',target)
console.log('depends on trigger key:', key)
console.log('depends on triggering deps:', deps)
deps.forEach(dep= >dep()); }}}Copy the code
HTML sample
Import the above code, create an app instance with createApp, define reactive data, and mount it.
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, Initial - scale = 1.0 "> < title > Document < / title > < / head > < body > < div id =" app "> < h1 > {{title}} < / h1 > < / div > < / body > < script src="./vue3x.js"></script> <script> const { createApp } = Vue; const app = createApp({ data () { return { title: "hello vue3!" } }, setup() { const state = reactive({ title: "hello vue3 setup" }) setTimeout(() => { state.title = " hello vue3 setup change" }, 2000) return state; } }) app.mount("#app"); </script> </html>Copy the code
conclusion
Compared with Vue2x, vue3X has a number of optimized updates, such as: Responsive mechanism changes (faster initialization, listening to any type, such as Array, Set, Map), functional (removing a lot of context this.xx calls, better support for TS, targeted packaging, tree shaking optimization), reuse (API, can be proposed a lot of common logic, Better reuse, comparison of mixins to which mixins data is not known, more explicit source, no naming conflicts), etc. For more, please go to the official documentation to read the experience and I recommend watching the vue CONF 2021 video.