Components are an important core of Vue, and we componentize the structure of the page when we engineer the project. Componentization means independence and sharing, and the two conclusions are not contradictory. Independent component development enables developers to focus on the development and expansion of a function item, and the design concept of component makes the function item more reusable, and different pages can share component functions. For developers, writing Vue components is the core foundation of mastering Vue development. Vue official website has also spent a lot of space to introduce the system and various methods of using components. In this section, we will dig into the internal source code of Vue components, understand the implementation ideas of component registration, and combine with the basic process of instance mount analysis component rendering mount introduced in the previous section, and finally we will analyze how components and components are connected. I believe that understanding these underlying implementation ideas will help us significantly in solving vUE component-related problems in the future.

5.1 There are two registration modes for Components

Those familiar with the Vue development process know that Vue components need to be registered before use, and there are two ways to register, global registration and local registration. Before we dive into the source code analysis, let’s recall the usage of the two so that we can learn the differences later.

5.1.1 Global Registration
Vue.component('my-test', {
    template: '<div>{{test}}</div>'.data () {
        return {
            test: 1212
        }
    }
})
var vm = new Vue({
    el: '#app',
    template: '<div id="app"><my-test><my-test/></div>'
})
Copy the code

The global registration of components needs to be invoked before the global Vue is instantiated, after which it can be invoked from any newly created Vue instance.

5.1.2 Partial registration
var myTest = {
    template: '<div>{{test}}</div>'.data () {
        return {
            test: 1212
        }
    }
}
var vm = new Vue({
    el: '#app',
    component: {
        myTest
    }
})
Copy the code

If a component needs to be used only in a part, you can register the component in the local registration mode. In this case, the partially registered component can only be used within the registered component.

5.1.3 Registration process

After a brief review of the two ways a component can be registered, let’s look at what happens during the registration process, using global component registration as an example. It passes Vue.component(name, {… }) for component registration, Vue.com Ponent is a static method defined during the introduction of Vue source code.

// Initialize the global API initAssetRegisters(Vue); var ASSET_TYPES = ['component'.'directive'.'filter'
];
functionInitAssetRegisters (Vue){// define methods forEach property of ASSET_TYPES, including component asset_types.foreach (function (type{/ /type: component,directive,filter
      Vue[type] = function (id,definition) {
          if(! Definition) {// Returns the constructor of the registered component directlyreturn this.options[type + 's'][id]
          }
          ...
          if (type= = ='component') {// validateComponentName validateComponentName(id); }if (type= = ='component'&& isPlainObject (definition)) {/ / component name set definition. The name = definition. The name | | id; // vue.extend () creates the child component and returns the subclass constructor definition = this.options._base.extend(definition); } // Add the subclass constructor this.options[to the Component property on vue. options.type + 's'][id] = definition;
          return definition
        }
    });
}
Copy the code

Vue.components takes two parameters, the name of the component to be registered and the component option. If the second parameter is not passed, the registered component option is returned. Otherwise, the component needs to be registered. During the registration process, the validity of the component name is checked to ensure that the component name does not contain invalid labels, including built-in component names such as Slot and Component.

function validateComponentName(name) {
    if(! new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "] * $").test(name)) {// Re determines whether to detect illegal tags warn('Invalid component name: "' + name + '". Component names ' +
        'should conform to valid custom element name in html5 specification.'); } // Do not use Vue's own custom component names, such as slot, component, do not use HTML reserved tags, such as H1, SVG, etcif (isBuiltInTag(name) || config.isReservedTag(name)) {
      warn(
        'Do not use built-in or reserved HTML elements as component ' +
        'id: '+ name ); }}Copy the code

After the component name is validated, the extend method is called to create a subclass constructor for the component, in which case this.options._base represents the Vue constructor. The extend method, defined in the section on option merging, creates a subclass based on the parent class, which in this case is Vue, inherits the methods of the parent class, merges the options of the parent class, and returns a subclass constructor.

There is also logic in the code that Vue.component() defaults to the first parameter as the component name, but if the component option has a name attribute, the name attribute value overrides the component name.

To sum up, the global registry component isVueCreate a base before instantiationVueClass constructor, and loads the component’s information into the instanceoptions.componentsIn the object.

** What is the implementation difference between local and global registrations? ** Instead of looking at the registration process for a local component, we’ll start with a globally registered component and see how its mounting process differs as a component.

5.2 Component Vnode Creation

In the last section we saw how Vue can render a template to generate a Vnode tree. In the case of no components, the last step of the _render function is to call new Vnode directly to create a complete Vnode tree. However, there is a large part of the branch that we do not analyze, and that is the scenario where we encounter component placeholders. If components are encountered during execution, the process is much more complicated than expected, which is illustrated by a flow chart.

5.2.1 Flowchart for Creating a Vnode

5.2.2 Specific process analysis

Let’s analyze this process with a practical example and a flow chart:

  • scenario
Vue.component('test', {
  template: '<span></span>'
})
var vm = new Vue({
  el: '#app',
  template: '<div><test></test></div>'
})
Copy the code
  • The fatherrenderfunction
function() {
  with(this){return _c('div',[_c('test')],1)}
}
Copy the code
  • VueRoot instance initialization is performedvm.$mount(vm.$options.el)The process of instance mounting, according to the previous logic, the whole process will go throughrenderFunction to generateVnode, as well asVnodeGenerate realDOMIn the process.
  • renderFunction to generateVnodeIn the process, the child takes precedence over the parentVnodeThe process, which is the process_c('test')The function is executed first.'test'Will first judge is ordinaryhtmlThe label is also a placeholder for the component.
  • If it is a common label, the command is executednew VnodeProcess, which is also the process we analyzed in the previous chapter; If it is a placeholder for a component, it is entered to determine that the component has already been registeredcreateComponentCreating child componentsVnodeIn the process.
  • createComponentCreate a componentVnode“, the creation process again merges the option configuration, installs the component-specific internal hooks (the purpose of which will be covered again in a later article), and finally passesnew Vnode()Generated byvue-componentAt the beginning ofVirtual DOM
  • renderThe function execution process is also a circular recursive call creationVnodeAfter 3 or 4 steps, a complete set of sub-components is generatedVnode tree

The implementation of the _createElement function was examined in the previous section, focusing on component-specific operations.

// Internally executes the function that converts the render function to Vnodefunction_createElement (context, tag, the data, the children, normalizationType) {...if (typeof tag === 'string') {// The tags of the child nodes are normal HTML tags that create vNodes directlyif(config.isReservedTag(tag)) { vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ); // The child node tag is the registered component tag name, then the child component Vnode creation process}else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options.'components'Vnode = createComponent(Ctor, data, Context, children, tag); }}}Copy the code

Config. isReservedTag(tag) is used to check whether the tag is a common HTML tag. If it is a common node, the Vnode will be created directly. If it is not, the placeholder component needs to check whether it is registered. Use Context.code.components.ponents to get the registered component options. To determine if a component is globally registered, look at the resolveAsset implementation.

// You need to make sure that the component is already registeredfunction resolveAsset (options,type,id,warnMissing) {// Tags are stringsif(typeof id ! = ='string') {
      return} // here is options.component var assets = options[type]; // The branch supports case and hump naming conventions respectivelyif (hasOwn(assets, id)) { return assets[id] }
    var camelizedId = camelize(id);
    if (hasOwn(assets, camelizedId)) { return assets[camelizedId] }
    var PascalCaseId = capitalize(camelizedId);
    if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] }
    // fallback to prototype chain
    var res = assets[id] || assets[camelizedId] || assets[PascalCaseId];
    if(warnMissing && ! res) { warn('Failed to resolve ' + type.slice(0, -1) + ':'+ id, options ); } // Finally returns the constructor for the subclassreturn res
  }
Copy the code

After getting the registered subclass constructor, call the createComponent method to create the child component Vnode

// Create a child componentfunctionCreateComponent (Ctor, // Subclass constructor data, context, // VM instance children, // Child node tag // child component placeholder) {··· // the _base property in vue. options stores the Vue constructor var baseCtor = context.$options._base; // For local component registration scenariosif(isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); } data = data || {}; / / the constructor configuration combined resolveConstructorOptions (Ctor); InstallComponentHooks (data); //returna placeholder vnode var name = Ctor.options.name || tag; Var vnode = new vnode (() var vnode = new vnode ()"vue-component-" + (Ctor.cid) + (name ? ("-" + name) : ' ')),data, undefined, undefined, undefined, context,{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },asyncFactory);

    return vnode
  }
Copy the code

This removes most of the code, leaving only the code for creating vNodes, which will eventually instantiate a Vnode with a name starting with vue-Component – via new Vue. Two of the key steps are configuring the merge and installing component hook functions. You can see the merge options in the first two sections of this series, where you see what installComponentHooks does when it installs component hook functions.

Var componentVNodeHooks = {init:function init (vnode, hydrating) {
    },
    prepatch: function prepatch (oldVnode, vnode) {
    },
    insert: function insert (vnode) {
    },
    destroy: functiondestroy (vnode) { } }; var hooksToMerge = Object.keys(componentVNodeHooks); // Merge the componentVNodeHooks function into the component data.hookfunction installComponentHooks (data) {
    var hooks = data.hook || (data.hook = {});
    for(var i = 0; i < hooksToMerge.length; i++) { var key = hooksToMerge[i]; var existing = hooks[key]; var toMerge = componentVNodeHooks[key]; // If the hook function exists, execute mergeHookThe $1Methods combinedif(existing ! == toMerge && ! (existing && existing._merged)) { hooks[key] = existing ? mergeHookThe $1(toMerge, existing) : toMerge; }}}function mergeHookThe $1(f1, f2) {// Return a function that executes f1,f2 successively var merged =function (a, b) {
      f1(a, b);
      f2(a, b);
    };
    merged._merged = true;
    return merged
  }
Copy the code

The component’s default hook functions are executed at different stages of the patch process, which is beyond the scope of this section.

5.2.3 Difference between local registration and global registration

The use of global and local registries leaves the question of what is the difference between the two. In fact, the principle of local registration is also simple. When we use a local registration component, we add the object configuration of the child component by components in the parent component option configuration. This is similar to the result of adding the child component constructor in Vue’s Options.com ponent after global registration. The difference is:

– 1. Objects added by local registration are configured under a component, while child components added by global registration are configured under the root instance. – 2. The local registry adds a configuration object for a child component, while the global registry adds a subclass constructor.

So missing from the local registry is a step to build a subclass constructor. Where does that go? Going back to the createComponent source code, there is a distinction between locally and globally registered components based on whether the option is an object or a function. If the value of the option is an object, then the component is locally registered. The extend method of the parent class is called to create a subclass constructor when a child Vnode is created.

functioncreateComponent (...) {... var baseCtor = context.$options._base; // For local component registration scenariosif(isObject(Ctor)) { Ctor = baseCtor.extend(Ctor); }}Copy the code

5.3 Component Vnode Renders the real DOM

According to the previous analysis, components are not instantiated, whether globally registered or locally registered. At what stage does the component instantiation process take place? Let’s look at how the Vnode Tree renders the real DOM.

5.3.1 Actual node rendering flow chart

5.3.2 Specific process analysis
    1. aftervm._render()Generate completeVirtual DomTree, then executeVnodeRender trueDOMThe process isvm.update()Method execution, which is at its corevm.__patch__.
    1. vm.__patch__The interior will passcreateElmTo create realDOMElement, during encounter childrenVnodeIt’s called recursivelycreateElmMethods.
    1. During recursive calls, determining whether the node type is a component type is passedcreateComponentMethod to judge the method and renderVnodeMethod of stagescreateComponentNo, it will call the child componentinitInitialize the hook function and complete the component’sDOMInsert.
    1. initThe core of initializing a hook function isnewInstantiate the child and mount it, and the process of instantiating the child goes back to merge configuration, initializing the life cycle, initializing the event center, initializing the render. The instance mount is executed again$mountProcess.
    1. After all the child components are instantiated and the node is mounted, the root node is finally mounted.

__patch__ core code is created through createElm real node, when encountered in the process of creating children vnode, invokes the createChildren, pair is the purpose of the createChildren vnode recursive call createElm create sub-components node.

// Create the real DOMfunctionCreateElm (vnode insertedVnodeQueue, parentElm refElm, nested, ownerArray, index) {... / / The real nodes of the child components are created recursively, and the real node insertion of the root node is not done until all the child components have been renderedif (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    return} ··· · var children = vnode.children; // createChildren(vnode, children, insertedVnodeQueue); ... the insert (parentElm, vnode. Elm, refElm); }function createChildren(vnode, children, insertedVnodeQueue) {
  for(var i = 0; i < children.length; -createElm createElm(children[I], insertedVnodeQueue, vnode.elm, null,true, children, i); }}Copy the code

The createComponent method processes the child Vnode. Remember that during the generation phase of the Vnode, a series of hook functions are installed for the child Vnode. In this step, we can determine whether the child Vnode has the defined hook function. Execute the component’s init hook.

The init hook does just two things: instantiate the component constructor and perform the mount process for the child components. (See the specific article analysis of the keep-alive branch)

functioncreateComponent (vnode, insertedVnodeQueue, parentElm, refElm) { var i = vnode.data; // A hook function can be used as the only criterion to determine whether it is a componentif(isDef(I = i.hook) &&isdef (I = i.init)) {// init hook function I (vnode,false/* hydrating */); } ··· ·} var componentVNodeHooks = {// Ignore keepAlive procedure // instantiate var child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance); / / mounted to the child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
functionCreateComponentInstanceForVnode (vnode, parent) {... / / instantiate the component instance Vuereturn new vnode.componentOptions.Ctor(options)
}

Copy the code

Obviously, the process of Vnode generating real DOM is also a continuous recursive process of creating child nodes. If the patch process encounters a child Vnode, it will instantiate the child component first and execute the mount process of the child component, which will return to the _render and _update process. After all the child VNodes are mounted recursively, the root node is finally actually mounted.

5.4 Establishing Component Relationships

In daily development, we can use vm.$parent to get the parent instance, or vm.$children to get the child component of the instance. Obviously, Vue establishes a layer of association between components. Next, we’ll explore how to establish relationships between components.

There is an initLifecycle process in the initialization instance phase for both parent and child instances. This process adds the current instance to the parent’s $children property and sets its own $parent property to point to the parent instance. ** Give a specific application scenario:

<div id="app">
    <component-a></component-a>
</div>
Vue.component('component-a', {
    template: '<div>a</div>'
})
var vm = new Vue({ el: '#app'}) console.log(vm) // outputs the instance objectCopy the code

Because vue instances have no parent, vm.$parent is undefined, and the $children attribute of VM points to instances of child component componentA.

The child component componentA’s $parent attribute points to its parent VM instance, and its $children attribute points to nothing

Source code analysis is as follows:

function initLifecycle (vm) {
    var options = vm.$options; Var parent = options.parent; var parent = options.parent; // If it is a child component and the component is not abstract, add the instance of that component to the parent component's$parentProperty, if the parent component is abstract, look up until the parent component is not abstract, and add the instance of that component to the parent component's$parentattributeif(parent && ! options.abstract) {while (parent.$options.abstract && parent.$parent) {
        parent = parent.$parent;
        }
        parent.$children.push(vm); } // add its own$parentProperty points to the parent instance. vm.$parent = parent;
    vm.$root = parent ? parent.$root : vm;

    vm.$children = [];
    vm.$refs = {};

    vm._watcher = null;
    vm._inactive = null;
    vm._directInactive = false; _isMounted = // Whether to mount vmfalse; // Whether the instance is destroyed vm._isdestroyed =false; // Whether the instance is being destroyed vm._isbeingDestroyed =false;
}

Copy the code

Finally, abstract components. There are many built-in abstract components in Vue, such as
,


, etc. These abstract components do not appear on the parent path, and they do not participate in DOM rendering.

5.5 summary

In Vue, we can define global components as well as local components. Global components need to be registered globally. The core method is Vue.com Ponent, which needs to be registered before the root component is instantiated. The reason is that we need to take the component’s configuration information and merge it into the Options.com ponents option before instantiating it. The essence of registration is to call extend to create a subclass constructor. The difference between global and local is that local subclass constructor creation occurs in the child component Vnode phase. The most critical step in the child Vnode creation phase is to define a number of hooks for internal use. With a complete Vnode tree, the generation of the actual DOM will proceed. At this stage, if a child component is encountered, the Vnode will instantiate the child constructor and complete the mount of the child component. After you recursively mount the child components, you eventually return to the root component. With the basics of components in hand, the next section focuses on the advanced uses of components.


  • An in-depth analysis of Vue source code – option merge (1)
  • An in-depth analysis of Vue source code – option merge (2)
  • In-depth analysis of Vue source code – data agents, associated child and parent components
  • In-depth analysis of Vue source code – instance mount, compile process
  • In-depth analysis of Vue source code – complete rendering process
  • In-depth analysis of Vue source code – component foundation
  • In-depth analysis of Vue source code – components advanced
  • An in-depth analysis of Vue source code – Responsive System Building (PART 1)
  • In – Depth Analysis of Vue source code – Responsive System Building (Middle)
  • An in-depth analysis of Vue source code – Responsive System Building (Part 2)
  • In-depth analysis of Vue source code – to implement diff algorithm with me!
  • In-depth analysis of Vue source code – reveal Vue event mechanism
  • In-depth analysis of Vue source code – Vue slot, you want to know all here!
  • In-depth analysis of Vue source code – Do you understand the SYNTAX of V-Model sugar?
  • In-depth analysis of Vue source – Vue dynamic component concept, you will be confused?
  • Thoroughly understand the keep-alive magic in Vue (part 1)
  • Thoroughly understand the keep-alive magic in Vue (2)