Analyzing the mount principle of vue from the source code

brief introduction

Before we talk about the mount principle, we will introduce four different versions of vue

It can be seen that there are two types of build versions, one is the full version and the other is the runtime version, excluding environment problems. What's the difference between the two? In short, the full version includes compilers, while the runtime version does not include compilers. It needs to be emphasized that the runtime version is about 30% smaller than the full version. In addition, Vue cli is the runtime version by default. Of course, the version can be freely selected according to the specific situation. Then, the changed version of vue-cli3 is as follows:

// vue.config.js
module.exports = {
    configureWebpack: {
        resolve: {
            alias: {
                'vue$': 'vue/dist/vue.esm.js'
            }
        }
    },
}

So when do I need a compiler? As shown in the following code

// If you pass in a string to the template option, you need the compiler
new Vue({
	template:<div>hi</div>
})

// No compiler required
new Vue({
	render: c => c (xx)
})

OK, after the popularization of science, let's go to our topic, Mount mode and principle of vue.

Mount mode of vue

As we all know, there are two main ways to mount VUE, one is el option and the other is $mount. In fact, no matter whether we set el option when instantiating VUE or not, if we want the VUE instance to have associated DOM elements, only vm.$mount method is the only way, as shown in the source code:

      if (vm.$options.el) {
        vm.$mount(vm.$options.el);
      }

So, the specific ways are as follows

	//1.el options - > template
	new Vue({
	    el:"#app",
	    template:"<div>Ha ha ha</div>"
	})
	//2.el options - > render
	new Vue({
	    el:"#app",
	    render:c=>c(App)
	})
	//3. $mount - > template option
	new Vue({
    	template:"<div>Ha ha ha</div>"
	}).$mount("#app")
	//4. $mount ->render
	new Vue({
    	render:c=>c(App)
	}).$mount("#app")

Next, we will analyze the principle of vm.$mount from the source code.

Mount principle of vue

As mentioned at the beginning of this article, vue construction is divided into full version and runtime, so the implementation principle of $mount will be different. First, please see the following code

  // public mount method
  Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };

This code is the $mount at run time. The specific logic will not be discussed first. Later, we will talk about the full version of $mount

The implementation principle of full version vm.$mount

ok, paste the source code first

  var mount = Vue.prototype.$mount;
  Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
      warn(
        "Do not mount Vue to <html> or <body> - mount to normal elements instead."
      );
      return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    if (!options.render) {
      var template = options.template;
      if (template) {
        if (typeof template === 'string') {
          if (template.charAt(0) === '#') {
            template = idToTemplate(template);
            /* istanbul ignore if */
            if (!template) {
              warn(
                ("Template element not found or is empty: " + (options.template)),
                this
              );
            }
          }
        } else if (template.nodeType) {
          template = template.innerHTML;
        } else {
          {
            warn('invalid template option:' + template, this);
          }
          return this
        }
      } else if (el) {
        template = getOuterHTML(el);
      }
      if (template) {
        /* istanbul ignore if */
        if (config.performance && mark) {
          mark('compile');
        }

        var ref = compileToFunctions(template, {
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines,
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this);
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns;
        options.render = render;
        options.staticRenderFns = staticRenderFns;

        /* istanbul ignore if */
        if (config.performance && mark) {
          mark('compile end');
          measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
        }
      }
    }
    return mount.call(this, el, hydrating)
  };

It can be seen that the full version uses function hijacking on the basis of runtime, Save the $mount method on the prototype on the mount attribute. Here, the $mount method is the code pasted at the beginning of the above chapter for subsequent use. Then, redefine a $mount method on the prototype. In this way, you can add some functions before executing the original method. Let me see what functions he added next?
1. First, parse the el, which is the incoming mounted object

 el = el && query(el);
  function query (el) {
    if (typeof el === 'string') {
      var selected = document.querySelector(el);
      if (!selected) {
        warn(
          'Cannot find element: ' + el
        );
        return document.createElement('div')
      }
      return selected
    } else {
      return el
    }
  }

As can be seen from the above code, you must first pass what you want, and then find this element. If you don't find it, issue a warning, and then create a new div. if you find it, return it directly.

2. Next, judge whether the el is html or body. If it is a warning, return to the vue instance directly

    if (el === document.body || el === document.documentElement) {
      warn(
        "Do not mount Vue to <html> or <body> - mount to normal elements instead."
      );
      return this
    }

3. Next is the difference between the full version and the runtime. That is to determine whether there is a render function. If not, compile template/el into a render function

   var options = this.$options;
   if (!options.render) {
    // Compiling process  
   }
   return mount.call(this, el, hydrating)

Note that the above $options are all the parameters passed in during instantiation. This value is assigned during initialization. It can be seen from this condition that if the render option is given, then the template is invalid. Let's see how to handle it if the template is valid
4. First, as shown in the source code

	  var template = options.template;
      if (template) {
        	if (typeof template === 'string') {
	          	if (template.charAt(0) === '#') {
		            template = idToTemplate(template);
		            /* istanbul ignore if */
		            if (!template) {
		              warn(
		                ("Template element not found or is empty: " + (options.template)),
		                this
		              );
		            }
		          }
		        } else if (template.nodeType) {
		          template = template.innerHTML;
		        } else {
		          {
		            warn('invalid template option:' + template, this);
		          }
		        return this
	        }
      } else if (el) {
        template = getOuterHTML(el);
      }

Determine whether the user provides the template option, that is, mount the template. If it does, and if the value starts with ා, it will be used as the selector and the innerHTML of the matching element will be used as the template. The common technique is to use < script type = "x-template" > to include templates. If template is a string but does not start with a #, it means that template is a template set by the user. It doesn't need to be improved. It can be used directly. If the template option is not a string, but a DOM element, the innerHTML of the DOM element is used as the template. If the template option is neither a string nor a DOM element, VUE will give a warning that the template option is invalid.

If not, then get the template from the el option provided by the user through the getOuterHTML method. The getOuterHTML logic is as follows

  function getOuterHTML (el) {
    if (el.outerHTML) {
      return el.outerHTML
    } else {
      var container = document.createElement('div');
      container.appendChild(el.cloneNode(true));
      return container.innerHTML
    }
  }

I don't want to talk about the above logic. I believe everyone can understand it. Then, with template, how can I parse it?
5. Parsing template

    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile');
      }

      var ref = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines: shouldDecodeNewlines,
        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this);
      var render = ref.render;
      var staticRenderFns = ref.staticRenderFns;
      options.render = render;
      options.staticRenderFns = staticRenderFns;

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end');
        measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
      }
    }

The focus here is on compileToFunctions, I'll just mention it briefly here, because template compilation needs to be extracted separately and written in a separate chapter. Here compileToFunctions is mainly to compile templates into rendering functions and set them to this.$options. Template compilation consists of three parts: parser, optimizer, code generator and resolver. Optimizer is mainly to parse templates into ast Traverse AST to mark the static node, so that when the node is updated in the virtual DOM, it will not be re rendered, and the code generator is to convert ast into a code string, so as to convert it into a rendering function.

This is the principle of the full version of vm.$mount. All the logic is described below. Let's talk about the runtime logic.

The principle of vm.$mount with only runtime version

  Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };

1. First, convert id to DOM element. As mentioned before, I will not explain it here
2. The mountcomponent function, whose function is to bind the vue instance to the DOM element and realize the persistence, that is, whenever the data changes, it can still be rendered to the specified DOM element.

function mountComponent (
  vm,
  el,
  hydrating
) {
  vm.$el = el;
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        );
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        );
      }
    }
  }
  callHook(vm, 'beforeMount');

  var updateComponent;
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = function () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      mark(startTag);
      var vnode = vm._render();
      mark(endTag);
      measure(("vue " + name + " render"), startTag, endTag);

      mark(startTag);
      vm._update(vnode, hydrating);
      mark(endTag);
      measure(("vue " + name + " patch"), startTag, endTag);
    };
  } else {
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }
    }
  }, true /* isRenderWatcher */);
  hydrating = false;

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}

First, judge whether there is a rendering function. If not, generate a default rendering function, that is, return a VNode node of annotation type, and issue a warning~
Then trigger the beforeMount life cycle hook. After the hook starts, it actually starts to mount. Because it is a persistent mount, the focus is on the use of the watcher, and this code needs to be noted

 vm._update(vm._render(), hydrating);

_The function of update is to call the patch method of virtual DOM to compare the old with the new, while the function of render is to generate a new number of VNode nodes, so the function of VM. Update (VM. Render(), hybridizing) is very obvious, that is, to compare the new VNode with the old VNode and update the DOM, that is to say, to perform the rendering operation~

OK, let's really share it here. What's wrong? I'm very glad to hear from you. Thank you

45 original articles published, 18 praised, 10000 visitors+
Private letter follow

Keywords: Vue Attribute

Added by OpSiS on Wed, 11 Mar 2020 06:33:20 +0200