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

  1. Implement the renderer extension
  2. Template initialization, data update

Create a Vue

  1. CreateApp creates an app instance
  2. CreateRenderer renderer, where createApp is the actual create function
  3. CreateRenderer -> mount The node to save data sources such as data setup passed in by the user
  4. CreateRenderer -> proxy intercepts data only for data and setup priorities, not for reactive triggering
  5. In the side effect function are the callbacks that are triggered when the actual data responds.
  6. 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.