I’ve had a lot of interviews lately, and in almost every interview I’ve had a question about Vue source code. Here to open a series of columns, to summarize the experience of this aspect, if you feel helpful, might as well click a like support.

preface

Vue has two core ideas, one is data-driven, which is simply to render the final DOM through templates and data. How to achieve this is described in detail in the previous 🚩Vue source code – how to render templates and data into the final DOM.

Another is componentization, called componentization, that is, a page is divided into multiple components, these components are independent, reusable, nested, such as these components development completed, like building blocks assembled into a page.

This article builds on the previous article by detailing how components are rendered into the final DOM in Vue, and how the process differs from rendering into the final DOM through templates and data.

Create a simple demo and explore the scenario based on this setup.

<! DOCTYPE html> <html> <head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"></div> </body> <script> const aa ={ template:'<div><p>{{aa}}<span>{{bb}}</span></p></div>', Data () {return {aa: 'welcome', bb: 'Vue'}}} var app = new Vue ({el: '# app, render: h = > h (aa)}) < / script > < / HTML >Copy the code

Review the last logical flowchart for rendering templates and data into the final DOMCompare this to the logical flowchart of the component rendering to the final DOM. 从new Vue() 到new Watcher()The process is always the same. Since the render method was compiled in the previous demo, in this demo the render method is handwritten by the userrender: h =>h(aa), so fromvm.renderThe beginning is not the same, so fromvm.renderStart by showing how components are rendered into the final DOM.

A, vm and _render

Vm. _update(vm._render(), hydrating), make a break point here and press F11 to enter vm._render() **.

Vue.prototype._render = function() {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    vm.$vnode = _parentVnode;
    var vnode;
    try {
        currentRenderingInstance = vm;
        vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
        //...
    } finally {
        currentRenderingInstance = null;
    }
    return vnode;
}
Copy the code

$createElement = vnode = render. Call (vm._renderProxy, vm.$createElement);

Vm. $createElement is defined by initRender(VM) in Vue initialization.

function initRender(vm) {
    vm._c = function(a, b, c, d) {
        return createElement(vm, a, b, c, d, false);
    };
    vm.$createElement = function(a, b, c, d) {
        return createElement(vm, a, b, c, d, true);
    };
}
Copy the code

You can see that calling vm.$createElement is actually calling createElement(vm, a, b, c, d, true).

Vnode = render. Call (vm._renderProxy, vm.$createElement) make a breakpoint here and press F11 three times to enter the createElement method.

1, _createElement

var SIMPLE_NORMALIZE = 1;
var ALWAYS_NORMALIZE = 2;
function createElement(context, tag, data, children, normalizationType, alwaysNormalize) {
    if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children;
        children = data;
        data = undefined;
    }
    if (isTrue(alwaysNormalize)) {
        normalizationType = ALWAYS_NORMALIZE;
    }
    return _createElement(context, tag, data, children, normalizationType)
}
Copy the code

Note that the parameter alwaysNormalize is true, so normalizationType has a value of 2.

Finally, call _createElement, make a breakpoint here, and press F11 to enter the _createElement method.

function _createElement(context, tag, data, children, normalizationType) {
    if (normalizationType === ALWAYS_NORMALIZE) {
        children = normalizeChildren(children);
    } else if (normalizationType === SIMPLE_NORMALIZE) {
        children = simpleNormalizeChildren(children);
    }
    var vnode;
    if (typeof tag === 'string') {
        //...
    } else {
        vnode = createComponent(tag, data, context, children);
    }
    return vnode
}
Copy the code

From the set scene, from the render: NormalizationType = 2 = ALWAYS_NORMALIZE You can also ignore the logical process of children = normalizeChildren(children).

The tag argument is an option object for component AA.

Const aa = {template: '< div > < p > < span > {{aa}} {{bb}} < / span > < / p > < / div >', the data () {return {aa: 'welcome', bb: 'Vue'}}}Copy the code

CreateComponent generates a vNode of the component type by following the else logic. Vnode = createComponent(Tag, data, Context, children), set a breakpoint here and press F11 to enter the createComponent method.

2, the createComponent

function createComponent(Ctor, data, context, children, tag) {
    var baseCtor = context.$options._base;
    if (isObject(Ctor)) {
        Ctor = baseCtor.extend(Ctor);
    }
    data = data || {};
    installComponentHooks(data);
    var name = Ctor.options.name || tag;
    var vnode = new VNode(
        ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
        data, undefined, undefined, undefined, context, {
            Ctor: Ctor,
            tag: tag,
            children: children
        }
    );
    return vnode
}
Copy the code

The above code has three key steps: constructing the subclass constructor, installing the component hook function, and instantiating the VNode.

1. Construct the subclass constructor baseCtor. Extend

var baseCtor = context.$options._base;
if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
}
Copy the code

_base is defined in the initGlobalAPI function.

function initGlobalAPI(Vue) {
    Vue.options._base = Vue;
}
Copy the code

Add options to $options in this._init(options).

vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
);
Copy the code

So baseCtor is actually the Vue constructor. Let’s look at the definition of vue. extend.

var ASSET_TYPES = ['component','directive','filter'];
Vue.extend = function(extendOptions) {
    extendOptions = extendOptions || {};
    var Super = this;
    var SuperId = Super.cid;
    var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
    if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
    }

    var name = extendOptions.name || Super.options.name;
    if (name) {
        validateComponentName(name);
    }

    var Sub = function VueComponent(options) {
        this._init(options);
    };
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    
    Sub.cid = cid++;
    Sub.options = mergeOptions(
        Super.options,
        extendOptions
    );
    Sub['super'] = Super;

    if (Sub.options.props) {
        initProps$1(Sub);
    }
    if (Sub.options.computed) {
        initComputed$1(Sub);
    }
    
    Sub.extend = Super.extend;
    Sub.mixin = Super.mixin;
    Sub.use = Super.use;
    
    ASSET_TYPES.forEach(function(type) {
        Sub[type] = Super[type];
    });
    
    if (name) {
        Sub.options.components[name] = Sub;
    }
    
    Sub.superOptions = Super.options;
    Sub.extendOptions = extendOptions;
    Sub.sealedOptions = extend({}, Sub.options);
    cachedCtors[SuperId] = Sub;
    return Sub
}
Copy the code

Vue.extend creates a subclass of Vue Sub.

var Sub = function VueComponent(options) {
    this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Copy the code

Here we use a combination of stereotype chain inheritance and borrowed constructor inheritance to create a subclass Sub from Vue and return it.

The VueComponent(options) constructor of subclass Sub calls the parent Vue initializer this._init(options). This instantiates the subclass Sub and executes this._init(options), which again goes through the initialization of the Vue.

Sub.prototype = new Vue(); sub.prototype = new Vue();

Sub.prototype = object.create (super.prototype) to achieve prototype chain inheritance, no longer call the parent class Vue. Then redirect the constructor of subclass Sub to Sub.

var Super = this;
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
    return cachedCtors[SuperId]
}
Sub.cid = cid++;
cachedCtors[SuperId] = Sub;
return Sub
Copy the code

During the creation process, subclass Sub is cached to avoid subclass Sub being created for the same component multiple times when vue. extend is executed.

Install the component hook function installComponentHooks

data = data || {};
installComponentHooks(data);
Copy the code

InstallComponentHooks Make a breakpoint here and press F11 to enter the installComponentHooks method.

var componentVNodeHooks = { init: function init(vnode, hydrating) { //... }, prepatch: function prepatch(oldVnode, vnode) { //... }, insert: function insert(vnode) { //... }, destroy: function destroy(vnode) { //... }}; var hooksToMerge = Object.keys(componentVNodeHooks); function mergeHook(f1, f2) { var merged = function(a, b) { f1(a, b); f2(a, b); }; merged._merged = true; return merged } function 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 (existing ! == toMerge && ! (existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge; }}}Copy the code

For installComponentHooks, use mergeHook to mergeHook functions from componentVNodeHooks into data.hook. In the mergeHook method, you return a function that executes the two hook functions in turn, completing the merge.

Remember in this section that data.hook stores the component’s hook function.

3, new VNode

var name = Ctor.options.name || tag;
var vnode = new VNode(
    ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
    data, undefined, undefined, undefined, context, {
        Ctor: Ctor,
        tag: tag,
        children: children
    }
);
return vnode
Copy the code

Take a look at the constructor of the VNode class, ignoring any code that is irrelevant to the scenario.

var VNode = function VNode(tag, data, children, text, elm, context, componentOptions, asyncFactory) {
    this.tag = tag;
    this.data = data;
    this.children = children;
    this.text = text;
    this.elm = elm;
    this.context = context;
    this.key = data && data.key;
    this.componentOptions = componentOptions;
    this.componentInstance = undefined;
}
Copy the code

Note that the vNode of a component is different from the vnode of a normal element. The vnode of a component does not have children.

After vm._render() is executed to generate vNode, go back to vm._update and analyze how vNode generates the real DOM.

Second, the vm. _update

Vue.prototype._update = function(vnode, hydrating) { var vm = this; var prevEl = vm.$el; var prevVnode = vm._vnode; var restoreActiveInstance = setActiveInstance(vm); vm._vnode = vnode; if (! prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { vm.$el = vm.__patch__(prevVnode, vnode); } restoreActiveInstance(); }Copy the code

PrevVnode = vm._vnode, vm._vnode is the Virtual DOM generated by the current Vue instance. Vm. _vnode is the first render in the set scene. PrevVnode = undefined, then vm._vnode = vnode to assign the Virtual DOM generated by the current Vue instance to vm._vnode.

PrevVnode is undefined, so vm.$el = vm. __Patch__ (vm.$el, vnode, hydrating, false).

In the previous article, you saw how vm.__patch__ is defined.

$el = vm.__patch__(vm.$el, vnode, hydrating, false);

1, the patch

function patch(oldVnode, vnode, hydrating, removeOnly) { const insertedVnodeQueue = [] if (isUndef(oldVnode)) {} else { const isRealElement = isDef(oldVnode.nodeType) if (! isRealElement && sameVnode(oldVnode, vnode)) {} else { if (isRealElement) { oldVnode = emptyNodeAt(oldVnode) } const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) createElm(vnode, insertedVnodeQueue, parentElm, nodeOps.nextSibling(oldElm)) if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) {} } } return vnode.elm }Copy the code
  • parameteroldVnode: The last Virtual DOM, the value set in the scene isvm.$el, it is<div id="app"></div>The DOM object;
  • parametervnode: Virtual DOM this time;
  • parameterhydrating: in non-server rendering casesfalse, can be ignored;
  • parameterremoveOnly: it is intransition-groupUsed in the scene, not in the setting scene, forfalse, can be ignored.

If oldVnode is not a Virtual DOM but a DOM object, convert oldVnode to a Virtual DOM with emptyNodeAt and the converted DOM object on elm assignment, so oldElm equals vm.$el, Nodeops. parentNode(oldElm) is used to obtain the parent DOM node of oldElm, which is body.

function emptyNodeAt (elm) {
    return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
//nodeOps.parentNode
function parentNode (node) {
    return node.parentNode
}
Copy the code

CreateElm (vnode, insertedVnodeQueue, parentElm, nodeops.NextSibling (oldElm)) Press F11 to enter the createElm method, nodeops.nextsibling (oldElm) fetch the nextSibling of oldElm.

2, createElm

function createElm( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
        return
    }
}
Copy the code
  • parametervnode: Virtual DOM;
  • parameterinsertedVnodeQueue: hook function queue;
  • parameterparentElmParameters:vnodeThe parent DOM object of the corresponding DOM object;
  • parameterrefElm: placeholder node object, for example, parametervnodeCorresponding to the next sibling of the DOM object;

In the setup scenario, to render the component into a DOM, the createComponent method is called. Press F11 here to enter the createComponent method.

3, the createComponent

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data;
    if (isDef(i)) {
        if (isDef(i = i.hook) && isDef(i = i.init)) {
            i(vnode, false);
        }
        if (isDef(vnode.componentInstance)) {
            initComponent(vnode, insertedVnodeQueue);
            insert(parentElm, vnode.elm, refElm);
            if (isTrue(isReactivated)) {
                reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
            }
            return true
        }
    }
}
Copy the code

Var I = vnode.data; IsDef (I = i.hook) &&isdef (I = i.it); The init method is defined in componentVNodeHooks, which are merged into data.hook with the installComponentHooks method.

I (vnode, false), make a breakpoint here and press F11 to enter the init method.

Also note the insert(parentElm, vnode.elm, refElm) code, which inserts the DOM generated by the component’s content into the parent node, as described below.

4, componentVNodeHooks. Init

var componentVNodeHooks = { init: function init(vnode, hydrating) { if (vnode.componentInstance &&! vnode.componentInstance._isDestroyed && vnode.data.keepAlive) { //... } else { var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance); child.$mount(hydrating ? vnode.elm : undefined, hydrating); }}},Copy the code

Vnode.componentinstance indicates the component instance. The component instance is not created yet. Therefore, vnode.componentInstance is undefined and else logic is used. Through createComponentInstanceForVnode method to create a Vue instance child, call $mount method mount components.

CreateComponentInstanceForVnode (vnode, activeInstance) here to make a breakpoint, press F11 to enter createComponentInstanceForVnode method.

5, createComponentInstanceForVnode

function createComponentInstanceForVnode(vnode, parent) {
    var options = {
        _isComponent: true,
        _parentVnode: vnode,
        parent: parent
    };
    var inlineTemplate = vnode.data.inlineTemplate;
    if (isDef(inlineTemplate)) {
        options.render = inlineTemplate.render;
        options.staticRenderFns = inlineTemplate.staticRenderFns;
    }
    return new vnode.componentOptions.Ctor(options)
}
Copy the code
  • parametervnode: The Virtual DOM generated by the component to render.
  • parameterparent: The parent Vue instance of the component to render, which is the context.

Remember that in the createComponent method, a Vue subclass (component) constructor is created with Ctor = baseCtor. Extend (Ctor) and assigned to Ctor, and then in the instantiation Vnode, The Vnode property componentOptions is passed as an argument to the Vnode constructor. So vnode.com ponentOptions. Ctor is to render the component constructor, a new generation component instance.

New vnode.com ponentOptions. Ctor (options), a breakpoint at this point, press F11 to enter. You’ll find yourself in the vue.extend method

var Sub = function VueComponent (options) {
    this._init(options);
}
Copy the code

Execute this._init(options) to initialize the component constructor, go back to the Vue constructor initialization, and press F11 to enter this._init to begin rendering the component content

6. Rendering process of component content

1, enclosing _init

Vue.prototype._init = function(options) { var vm = this; if (options && options._isComponent) { initInternalComponent(vm, options); } else { //... } if (vm.$options.el) { vm.$mount(vm.$options.el); }}Copy the code

Because in createComponentInstanceForVnode method set up the options = {_isComponent: Execute initInternalComponent(VM, options) to merge options, and break into the initInternalComponent method.

2, initInternalComponent

function initInternalComponent(vm, options) { var opts = vm.$options = Object.create(vm.constructor.options); var parentVnode = options._parentVnode; opts.parent = options.parent; opts._parentVnode = parentVnode; var vnodeComponentOptions = parentVnode.componentOptions; opts.propsData = vnodeComponentOptions.propsData; opts._parentListeners = vnodeComponentOptions.listeners; opts._renderChildren = vnodeComponentOptions.children; opts._componentTag = vnodeComponentOptions.tag; if (options.render) { opts.render = options.render; opts.staticRenderFns = options.staticRenderFns; }}Copy the code

Remember that the following code was executed when the vue.extend method was used to create the component’s constructor

Vue.extend = function(extendOptions){
    Sub.options = mergeOptions(
        Super.options,
        extendOptions
    );
}
Copy the code

Extend is called in the createComponent method with Ctor = baseCtor. Extend (Ctor). The parameter Ctor is the option object of the AA component in the demo, that is, extendOptions is

{template: '< div > < p > < span > {{aa}} {{bb}} < / span > < / p > < / div >', the data () {return {aa: 'welcome', bb: 'Vue'}}}Copy the code

After the mergeOptions method is merged, you can access the option object of the AA component in demo through the constructor property Options. Var opts = vm.$options = object. create(vm. Constructive. options) var opts = vm.

Opts.parent = opts.parent; opts._parentVnode = parentVnode; Combine before by createComponentInstanceForVnode function into several parameters to the vm. The $options.

  • vm.$options.parentVnode: The Virtual DOM generated by the component to render.
  • vm.$options.parent: The parent Vue instance of the component to render, which is the context.

$options. El){vm.$mount(vm.$options.el)}.

Back in the ComponentVNoderest. init hook function, execute child.$mount(hydrating? vnode.elm : Child.$mount(undefined, false) $mount(undefined, false) Press F11 to enter the child.$mount method.

3, the child $mount

var mount = Vue.prototype.$mount; Vue.prototype.$mount = function(el, hydrating) { var options = this.$options; if (! options.render) { var template = options.template; if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { //... } } else if (template.nodeType) { template = template.innerHTML; } else { //... } } else if (el) { template = getOuterHTML(el); } if (template) { 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; } } return mount.call(this, el, hydrating) };Copy the code

In the initInternalComponent method, merge the component option object into this.$options, var template = options.template, Template for the < p > < span > {{aa}} {{bb}} < / span > < / p >. Options. render is undefined so call compileToFunctions to convert template to render and mount it to this.$options.

Call return mount.call(this, el, hydrating), make a breakpoint here and press F11 to enter the mount method.

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

Notice that el is undefined. Return mountComponent(this, EL, hydrating), make a breakpoint here and press F11 to enter the mountComponent method.

4, mountComponent

function mountComponent(vm, el, hydrating) { vm.$el = el; var updateComponent; updateComponent = function() { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, { before: function before() { if (vm._isMounted && ! vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true); hydrating = false; if (vm.$vnode == null) { vm._isMounted = true; callHook(vm, 'mounted'); } return vm }Copy the code

At this point, the VM represents a component instance, not a Vue instance. Vm.$el = undefined, vm.$el = undefined.

Instantiate a render Watcher, which executes the callback function at initialization, vm._update(vm._render(), hydrating), and press F11 to enter the vm._render method.

5, vm and _render

Vue.prototype._render = function() {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var _parentVnode = ref._parentVnode;
    vm.$vnode = _parentVnode;
    var vnode;
    vnode = render.call(vm._renderProxy, vm.$createElement);
    vnode.parent = _parentVnode;
    return vnode
};
Copy the code

Vm $vnode said Vue instance of the parent Virtual DOM, its value vm. $options. Is _parentVnode createComponentInstanceForVnode (vnode, parent), Var options = {_parentVnode: $options = vnode,};};}; It can also be called the parent Virtual DOM of the component instance, as shown below.

The Render method at this point is shown below

(function anonymous() {
    with(this) {
        return _c('div', [_c('p', [_v(_s(aa)), _c('span', [_v(_s(bb))])])])
    }
})
Copy the code

Call vnode = render. Call (vm._renderProxy, vm.$createElement) to generate the contents of the component into a VNode (Virtual DOM) tree.

Call vnode.parent = _parentVnode to assign the parent Virtual DOM of the component content to vnode.parent and return vnode, as shown below.

Press F11 to enter the vm._update method.

6, vm. _update

Vue.prototype._update = function(vnode, hydrating) { var vm = this; var prevEl = vm.$el; var prevVnode = vm._vnode; var restoreActiveInstance = setActiveInstance(vm); vm._vnode = vnode; if (! prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { vm.$el = vm.__patch__(prevVnode, vnode); } restoreActiveInstance(); };Copy the code

$el = vm.__patch__(vm.$el, vnode, hydrating, false); Press F11 to enter the patch method.

7, patch

function patch(oldVnode, vnode, hydrating, removeOnly) {
    var isInitialPatch = false;
    var insertedVnodeQueue = [];
    if (isUndef(oldVnode)) {
        isInitialPatch = true;
        createElm(vnode, insertedVnodeQueue);
    } else {
        
    }
    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
}
Copy the code

OldVnode is undefined, so execute createElm(vnode, insertedVnodeQueue), make a breakpoint here, press F11 to enter createElm.

8 createElm.

function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { var children = vnode.children; var tag = vnode.tag; if (isDef(tag)) { vnode.elm = nodeOps.createElement(tag, vnode); createChildren(vnode, children, insertedVnodeQueue); insert(parentElm, vnode.elm, refElm); } else if (isTrue(vnode.isComment)) { } else { vnode.elm = nodeOps.createTextNode(vnode.text); insert(parentElm, vnode.elm, refElm); }}Copy the code

The createElm method is used to create a real DOM node and insert the corresponding parent node. A detailed introduction can be found in one of the articles.

Run createChildren(vnode, children, insertedVnodeQueue) and press F11 to enter the createChildren method.

9 createChildren.

function createChildren(vnode, children, insertedVnodeQueue) { if (Array.isArray(children)) { checkDuplicateKeys(children); for (var i = 0; i < children.length; ++i) { createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i); } } else if (isPrimitive(vnode.text)) { nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text))); }}Copy the code

CreateChildren’s logic is simple. It actually traverses the vNode child Virtual DOM, recursively calling createElm, a common depth-first traversal algorithm. In the traversal process, children[I] (Virtual DOM) of vnode.elm will be passed in corresponding to the parent node of the real DOM.

When children are not an array. Call nodeops.createTextNode to generate a plain text node and insert nodeops.appendChild into vnode.elm.

Insert (parentElm, vnode.elm, refElm) into the corresponding parent node (parentElm). Since it is a recursive call, the child Virtual DOM calls insert first, so the insertion order of the entire Virtual DOM tree after generating the real DOM is child first and then parent. Make a breakpoint at insert(parentElm, vnode.elm, refElm) and press F11 to enter the insert method.

10 and the insert

function insert(parent, elm, ref$$1) { if (isDef(parent)) { if (isDef(ref$$1)) { if (nodeOps.parentNode(ref$$1) === parent) { nodeOps.insertBefore(parent, elm, ref$$1); } } else { nodeOps.appendChild(parent, elm); }}}Copy the code
  • parameterparent: The parent node of the node to be inserted
  • parameterelm: Node to be inserted
  • parameterref$$1: reference node, will be inserted before the reference node

Nodeops. insertBefore corresponds to the insertBefore method, nodeops. appendChild corresponds to the appendChild method,

function insertBefore (parentNode, newNode, referenceNode) {
    parentNode.insertBefore(newNode, referenceNode);
}
function appendChild (node, child) {
    node.appendChild(child);
}
Copy the code

The insertBefore and appendChild methods call the native DOM API for DOM manipulation.

11. Back to createElm

Insert (parentElm, vnode.elm, refElm); CreateElm (vnode, insertedVnodeQueue); parentElm = undefined;

7. Back to createComponent

function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data;
    if (isDef(i)) {
        var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
        if (isDef(i = i.hook) && isDef(i = i.init)) {
            i(vnode, false);
        }
        if (isDef(vnode.componentInstance)) {
            initComponent(vnode, insertedVnodeQueue);
            insert(parentElm, vnode.elm, refElm);
            if (isTrue(isReactivated)) {
                reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
            }
            return true
        }
    }
}
Copy the code

The execution of the I (vnode, false), execute the var child ponentInstance = = vnode.com createComponentInstanceForVnode (vnode, activeInstance), So vnode.componentInstance has a value for the current component instance.

Execute initComponent(vnode, insertedVnodeQueue) and press F11 to enter the initComponent method.

function initComponent(vnode, insertedVnodeQueue) {
    vnode.elm = vnode.componentInstance.$el;
}
Copy the code

$el = vm.__patch__(vm.$el, vnode, hydrating, false).$el = vm.__patch__(vm.$el, vnode, hydrating, false).

Here the vNode generates the Virtual DOM for the component, not the component content.

Insert (parentElm, vnode.elm, refElm) where parentElm is the parent node of the component, in this case body, to insert the DOM tree generated by the component’s content into the body.

Third, summary

Compare the process of rendering data and templates (common element tags) to the DOM. In the process of rendering components to the DOM, the createComponent method is called during vm.render to generate a vNode of the component type. Call Vue. Extend to create a component constructor that inherits from Vue. During the execution of vm.updata, the createElm method is called to generate a real DOM from the Virtual DOM. In the createComponent method, the component’s hook function init is executed to instantiate the component’s constructor, and the process of rendering to DOM is repeated. If the contents of the component are all common element tags, the data and template will be rendered into DOM. After generating a real DOM tree, the createComponent method will be used to call insert method to insert it into the parent node of the component and render it on the page. If the content of the component contains component tags, the component renders into the DOM again. Until all child components are normal element tags, the createComponent method calls insert to insert the content into the parent node of the parent component and renders into the page.