Preface
Last time we mentioned that in the case of a child component, the parent component generates an instance of the child component after the created hook function is executed, the child component executes the created hook function, and checks if there are also child components, or repeats the steps of the parent component, otherwise the child component's dom element renders
Learn more about vnode
In the last article Article In fact, we mentioned a function--createComponentInstanceForVnode.
function createComponentInstanceForVnode ( vnode, // we know it's MountedComponentVNode but flow doesn't parent // activeInstance in lifecycle state ) { var options = { _isComponent: true, _parentVnode: vnode, parent: parent }; // check inline-template render functions var inlineTemplate = vnode.data.inlineTemplate; if (isDef(inlineTemplate)) { options.render = inlineTemplate.render; options.staticRenderFns = inlineTemplate.staticRenderFns; } return new vnode.componentOptions.Ctor(options) }
Code associated with it_
... var child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ); child.$mount(hydrating ? vnode.elm : undefined, hydrating);
We can learn from this:
- Instances of subcomponents are generated by createComponentInstanceForVnode
- The above conclusions relate to vnode.componentOptions.Ctor(options)
VNode
By retrieving componentOptions globally, the following code exists_
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; ... }
In fact, there's a strange piece of code between the beforeMount hook and the mounted hook_
new Watcher(vm, updateComponent, noop, { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */);
Looking at the previous article, you already know Watcher's execution logic:
- Initialize related properties, including getter properties
- Execute getter while assigning value
Attach the updateComponent implementation:
updateComponent = function () { vm._update(vm._render(), hydrating); };
This means that the function updateComponent will be executed with such a call sequence (top-down):
- vm._render
- vm_update
dom elements must also be rendered on both function calls
vm._render
Vue.prototype._render = function () { var vm = this; var ref = vm.$options; var render = ref.render; var _parentVnode = ref._parentVnode; if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ); } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. vm.$vnode = _parentVnode; // render self var vnode; try { // There's no need to maintain a stack becaues all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm; vnode = render.call(vm._renderProxy, vm.$createElement); } catch (e) { handleError(e, vm, "render"); // return error render result, // or previous vnode to prevent render error causing blank component /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e); } catch (e) { handleError(e, vm, "renderError"); vnode = vm._vnode; } } else { vnode = vm._vnode; } } finally { currentRenderingInstance = null; } // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0]; } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ); } vnode = createEmptyVNode(); } // set parent vnode.parent = _parentVnode; return vnode };
Comb the execution of function_render (top-down):
- Assign_parentVnode (parent component's vnode) to vm.$vnode
- Execute normallizeScopedSlots to merge $slots of parent and child components with $scopedSlots
- Execute render function and assign to vnode (that is, get existing vnode)
- Execute the createEmptyVNode function if the vnode is empty
- Return vnode
Here we give priority to breakpoints in render functions, which naturally result in the following execution processes:
- render
- vm.$createElement
Since new Vue({...}) was first executed, it looks like the breakpoint stopped at "strange place"
new Vue({ render: h => h(App), }).$mount('#app')
render function
Careful classmates will notice such a line of code.
vnode = render.call(vm._renderProxy, vm.$createElement);
Breakpoint jumps here immediately after step_
new Vue({ render: h => h(App), ... }).$mount('#app')
In fact, here vm._renderProxy is used as the context object for the render function, and vm.$createElement returns a closure function as an argument to the render function
Related code:
vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
summary
Summarize the complete logic for generating vnode s:
- Execute the $mount function
- Determines whether or not in the browser environment, then gets the dom element and assigns it to the el variable, otherwise the el variable takes undefined
- Execute mountComponent function
- Execute new Watcher(vm, updateComponent, noop,...)
- Because of Watcher's "attributes" (the incoming updateComponent is assigned to the getter and executed after), the _render function is triggered after that
- Execute $createElement
- Execute createElement
- Execute_createElement
- To determine if the parameter data and data.is not empty, assign data.is to tag
- If the tag is empty, then this is considered a blank node, call createEmptyVNode to create a "blank node" and mark isComment as true
- Determine if the tag is a "reserved word" or an HTML tag, generating the corresponding vnode, otherwise call the createComponent function to generate the corresponding vnode?
- Return to vnode last
correlation function
createEmptyVNode
var createEmptyVNode = function (text) { if ( text === void 0 ) text = ''; var node = new VNode(); node.text = text; node.isComment = true; return node };