Vue version: 2.5.17-beta.0
In " Vue source notes - data driven - Mount Vue instance >As mentioned at the end of the article, the Vue instance is finally mounted to the final dom through VM. Update (VM. Render(), drafting)< Vue source notes - data driven - render >To record the VM. ﹐ render() method in, call vm.$createElement to return vnode data, and then record the normal node to call VM. ﹐ update process.
In the following example, a normal node is passed in for rendering and mounting to the dom:
new Vue({ el: '#app', data () { return { message: 'Hello Vue!' } }, render (h) { return h('div', { attrs: { id: 'app' } }, this.message) } })
The VM. Update prototype method is in the src/core/instance/lifecycle.js file. The source code is as follows:
export let activeInstance: any = null Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance // Record the current vm instance activeInstance = vm vm._vnode = vnode // Render vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render // First render incoming real dom vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
As can be seen from the above source code, three variables prevl, prevVnode and prevActiveInstance are defined first, but prevlode and prevActiveInstance are empty when rendering for the first time, prevl = vm.$el is the real dom node (vm.$el defined in the mountComponent method), then active instance is defined as the current instance, and VM. vnode is the vnode object returned by VM. Render() method.
At this point, we can see that when rendering for the first time, the logical call VM. \\\\\\\\\\\\\\\\\\
Vue.prototype.__patch__ = inBrowser ? patch : noop
The imported patch is in the src/platforms/web/runtime/patch.js file. The source code is as follows:
import * as nodeOps from 'web/runtime/node-ops' import { createPatchFunction } from 'core/vdom/patch' import baseModules from 'core/vdom/modules/index' import platformModules from 'web/runtime/modules/index' // the directive module should be applied last, after all // built-in modules have been applied. const modules = platformModules.concat(baseModules) export const patch: Function = createPatchFunction({ nodeOps, modules })
From the above source code, it can be seen that the path method finally defined is actually generated by calling createPatchFunction, while the nodeOps option passed in is a collection, an api collection for real dom operations, and the modules option is some operations on dom attributes during the patch, such as class, style, etc., so VM. \\\\\\\\\\.
The createPatchFunction method is defined in the src/core/vdom/patch.js file (because the source code is too long, not all of them will be displayed). Finally, a patch method is returned. The source code is as follows:
return function patch (oldVnode, vnode, hydrating, removeOnly) { // Delete logical subcomponent destroy if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] // Call insert hook to use // Logical rendering of subcomponents if (isUndef(oldVnode)) { // empty mount (likely as component), create new root element isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { // Determine whether it is a real dom const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) } else { // First render to real dom if (isRealElement) { // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. // Server rendering related if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } // Server rendering related if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } else if (process.env.NODE_ENV !== 'production') { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode) // Convert real dom to vnode object } // replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node // Mount vnode to real dom createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // update parent placeholder node element, recursively if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. const insert = ancestor.data.hook.insert if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm }
As mentioned before, the first rendering call in VM. Update is VM. El = VM. Patch (VM. El, vnode, drafting, false / * removeonly * /), which is actually to call the path method of the above source code. The first parameter passed in has a value and is the real DOM, so if (isUndef(oldVnode)) logic and else logic are not used. The isRealElement defined in it is true, so if (! Isrea) logic is not used Llelement & & samevnode (oldvnode, vnode)) logic, else logic, if (isRealElement) logic (service side rendering related logic omitted), call emptyNodeAt method to convert real DOM into vnode object, then get oldElm and parentElm dom nodes, and call createElm method. This method is to mount vnode into real dom. The source code is as follows:
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { if (isDef(vnode.elm) && isDef(ownerArray)) { // This vnode was used in a previous render! // now it's used as a new node, overwriting its elm would cause // potential patch errors down the road when it's used as an insertion // reference node. Instead, we clone the node on-demand before creating // associated DOM element for it. vnode = ownerArray[index] = cloneVNode(vnode) } vnode.isRootInsert = !nested // for transition enter check // Attempt to create component node if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag // Insertion order: first child, then parent, last parent is mounted on the document dom if (isDef(tag)) { // Element node if (process.env.NODE_ENV !== 'production') { if (data && data.pre) { creatingElmInVPre++ } if (isUnknownElement(vnode, creatingElmInVPre)) { warn( 'Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.', vnode.context ) } } vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) /* istanbul ignore if */ if (__WEEX__) { // in Weex, the default insertion order is parent-first. // List items can be optimized to use children-first insertion // with append="tree". const appendAsTree = isDef(data) && isTrue(data.appendAsTree) if (!appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } createChildren(vnode, children, insertedVnodeQueue) if (appendAsTree) { if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } } else { // Determine whether the child node recursively calls createElm and inserts it into the parent dom createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } // Parent mount node current vnode node reference node // The parentElm is undefined when the component is in use insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== 'production' && data && data.pre) { creatingElmInVPre-- } } else if (isTrue(vnode.isComment)) { // Comment Nodes vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) } else { // Text node vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) } }
The main logic of the above source code is to first call the createComponent method to try to create a component node. Here we are the normal node, so we return it as undefined. Then we get the data, children and tag from the incoming VNode object. The corresponding data are {attrs: {id: "app"}}}, [VNode] ('Hello Vue! 'text VNode object) and div tag. After that, we will judge the tag and pass the nodeOps api creates real dom, and finally calls insert to insert the parent node in html, that is, the incoming parentElm parameter is the parent node in html.
After the createElm method is run, the following code will be run at the end of the patch method:
if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0) }
Why do you need to execute this method for the last time? Because the newly created dom node is inserted in the createElm method, and the old node in html, that is, oldVnode, still exists. Therefore, you need to call this method to determine whether to delete the old node.
Now the VM. Update process is recorded.