Vue.js source code learning-data driven

Vue.js source code learning-data driven

Vue.js source code learning-data driven

Vue.js
A core idea is data-driven. The so-called data-driven means that the view is generated by data-driven, and the modification of the view does not directly operate
DOM
, But by modifying the data. Compared with traditional front-end development, such as using
jQuery
Wait for the library to directly modify
DOM
, Which simplifies the amount of code. When the interaction is complex, only caring about data modification will make the logic of the code very clear, because
DOM
Become a data mapping, all logic is to modify the data without touching
DOM
, The code is conducive to maintenance.

For example, the following uses template string syntax to render the data as

DOM
:

< div > {{ message }} </div > Copy code
var app = new Vue({ el : '#app' , data : { message : 'Hello Vue!' , }, }) Copy code

Will eventually be rendered on the page

Hello Vue!
. The goal of this article is to figure out how the template and data are rendered into the final
DOM
.

1. What happened to new Vue

Let's analyze first

new Vue
What happened.
Vue
In fact, it is a class, take a look at the source code, in
src/core/instance/index.js
in.

function Vue ( options ) { if (process.env.NODE_ENV !== 'production' && !( this instanceof Vue)) { warn( 'Vue is a constructor and should be called with the `new` keyword' ) } this ._init(options) } Copy code

You can know from the code

Vue
Can only pass
new
The keyword is initialized, and then it will call
this._init
method. The method is
src/core/instance/init.js
Defined in.

Vue
Initialization mainly does several things, merging configuration, initializing life cycle, initializing event center, initializing rendering, initializing data, props, computed, watcher, etc.

Look at the following piece of code, which is the end of the method.

if (vm.$options.el) { vm.$mount(vm.$options.el) } Copy code

At the end of initialization, it is detected that if there is

el
Attribute, call
vm.$mount
Method mount
vm
, The goal of mounting is to render the template into the final
DOM
.

2. the implementation of Vue instance mounting

Vue
Middle is passed
$mount
Instance method mount
vm
of,
$mount
The method is defined in multiple files, because the implementation of this method is related to the platform and construction method. Here is the key analysis zone
compiler
Version of
$mount
achieve.

Let's take a look at the source code first, in

src/platform/web/entry-runtime-with-compiler.js
Defined in the file:

const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document .body || el === document .documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body>-mount to normal elements instead.` ) return this } const options = this .$options //resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if ( typeof template === 'string' ) { if (template.charAt( 0 ) === '#' ) { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template} ` , this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production' ) { warn( 'invalid template option:' + template, this ) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark( 'compile' ) } const {render, staticRenderFns} = compileToFunctions( template, { outputSourceRange : process.env.NODE_ENV !== 'production' , shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters : options.delimiters, comments : options.comments, }, this ) 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' ) } } } return mount.call( this , el, hydrating) } Copy code

This code first caches the

$mount
Method, and then redefine the method. The key logic in the code is: if it is not defined
render
Method, it will put
el
or
template
String is converted to
render
method. Finally, call the original prototype
$mount
Method to mount.

On the original prototype

$mount
Method in
src/platform/web/runtime/index.js
Defined in. You can see below
$mount
What does the method mainly do:

//public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser? query(el): undefined return mountComponent( this , el, hydrating) } Copy code

$mount
Receive two parameters, the first is the mounted element, which can be a string or
DOM
Object, if it is a string, it will be called in the browser environment
query
Method is converted to
DOM
Object.
$mount
Method will actually call
mountComponent
method. This method is defined in
src/core/instance/lifecycle.js
In the file:

export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { 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' ) let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start: ${id} ` const endTag = `vue-perf-end: ${id} ` mark(startTag) const 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 = () => { 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 (eg inside child //component's mounted hook), which relies on vm._watcher being already defined new Watcher( vm, updateComponent, noop, { 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 } Copy code

The core is to define first

updateComponent
Method, and then call in the method
vm._render
Method first generates VNode and then calls
vm._update
Update the DOM. Then instantiate a render
Watcher
,will
updateComponent
The method is passed in, and the method will be called afterwards.

Watcher
There are two functions here, one is to execute the callback function during initialization, and the other is when
vm
The callback function is executed when the data in the instance changes.

Set when the function is finally judged to be the root node

vm._isMounted
for
true
, Which means that this instance has been mounted, and executed at the same time
mounted
Hook function. This attention
vm.$vnode
Representation
Vue
Virtual
Node
, So it is
Null
It means that the current is the root
Vue
Instance.

3. render

Let's take a look

Vue
of
_render
Method, this method is a private method of the instance, mainly used to render the instance into a
VNode
, Defined in
src/core/instance/render.js
In the file:

Vue.prototype._render = function (): VNode { const vm: Component = this const {render, _parentVnode} = vm.$options 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 let vnode try { //There's no need to maintain a stack because 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 } Copy code

The most important thing is the call of the render method, as follows:

vnode = render.call (vm._renderProxy, vm. $ createElement) copying the code

In the code above

vm.$createElement
The method definition is to implement
initRender
In the method, you can see that in addition to
vm.$createElement
Method, there is another
vm._c
Method, it is compiled into a template
render
Function usage, but
vm.$createElement
User-written
render
The method is used, the internal is called
createElement
Method.

and so

vm._render
In the end it is through execution
createElement
Method and return is
VNode
. Let's analyze it below
createElement
The realization.

4. createElement

createElement
Method is used to create
VNode
, It is defined in
src/core/vdom/create-element.js
In the file:

//wrapper function for providing a more flexible interface //without getting yelled at by flow export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array < VNode > { 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 code

createElement
The method is actually right
_createElement
The encapsulation of the method allows the incoming parameters to be more flexible. After processing these parameters, then call the real creation
VNode
The function
_createElement
:

export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object , data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array < VNode > { //... //Normalization of children if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } //Creation of VNode let vnode, ns if ( typeof tag === 'string' ) { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { //platform built-in elements if ( process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) ) { warn( `The .native modifier for v-on is only valid on components but it was used on < ${tag} >.` , context ) } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined , undefined , context ) } else if ( (!data || !data.pre) && isDef((Ctor = resolveAsset(context.$options, 'components' , tag))) ) { //component vnode = createComponent(Ctor, data, context, children, tag) } else { //unknown or unlisted namespaced elements //check at runtime because it may get assigned a namespace when its //parent normalizes children vnode = new VNode(tag, data, children, undefined , undefined , context) } } else { //direct component options/constructor vnode = createComponent(tag, data, context, children) } if ( Array .isArray(vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() } } Copy code

_createElement
The method has 5 parameters,
context
Means
VNode
Context, it is
Component
Types of;
tag
Represents the label, it can be a string or a
Component
data
Means
VNode
Data, it is a
VNodeData
Types of;
children
Means current
VNode
The child node, which is of any type, needs to be standardized as a standard
VNode
Array
normalizationType
Indicates the type of child node specification. Different types of specifications have different methods. It is mainly for reference
render
Whether the function is compiled or handwritten by the user.

Actually,

_createElement
The method mainly has 2 key processes,
children
Standardization and
VNode
Creation.

4.1. Standardization of children

Every

VNode
There may be several child nodes, and these child nodes should also be
VNode
type. due to
_createElement
4th parameter received
children
Are of any type, so they need to be normalized to
VNode
Types of. according to
normalizationType
Is different, called
normalizeChildren(children)
with
simpleNormalizeChildren(children)
method. They are defined in
src/core/vdom/helpers/normalize-children.js
In the file:

export function simpleNormalizeChildren ( children: any ) { for ( let i = 0 ; i <children.length; i++) { if ( Array .isArray(children[i])) { return Array .prototype.concat.apply([], children) } } return children } export function normalizeChildren ( children: any ):? Array < VNode > { return isPrimitive(children) ? [createTextVNode(children)] : Array .isArray(children) ? normalizeArrayChildren(children) : undefined } Copy code

simpleNormalizationChildren
The method call scenario is
render
The function is generated by template compilation. The role is to pass
Array.prototype.concat
Method to put the whole
children
The array is flattened so that its depth is only one level.

normalizeChildren
There are two kinds of method call scenarios, one scenario is
render
The function is handwritten by the user, when
children
When there is only one node, and it is the basic type, in this case, it will call
createTextVNode
Create a text node
VNode
; Another scenario is when compiling
slot
,
v-for
When the nested array will be generated, it will call
normalizeArrayChildren
Method, here is its implementation:

function normalizeArrayChildren ( children: any, nestedIndex?: string ): Array < VNode > { const res = [] let i, c, lastIndex, last for (i = 0 ; i <children.length; i++) { c = children[i] if (isUndef(c) || typeof c === 'boolean' ) continue lastIndex = res.length- 1 last = res[lastIndex] //nested if ( Array .isArray(c)) { if (c.length> 0 ) { c = normalizeArrayChildren(c, ` ${nestedIndex || '' } _ ${i} ` ) //merge adjacent text nodes if (isTextNode(c[ 0 ]) && isTextNode(last)) { res[lastIndex] = createTextVNode(last.text + (c[ 0 ]: any).text) c.shift() } res.push.apply(res, c) } } else if (isPrimitive(c)) { if (isTextNode(last)) { //merge adjacent text nodes //this is necessary for SSR hydration because text nodes are //essentially merged when rendered to HTML strings res[lastIndex] = createTextVNode(last.text + c) } else if (c !== '' ) { //convert primitive to vnode res.push(createTextVNode(c)) } } else { if (isTextNode(c) && isTextNode(last)) { //merge adjacent text nodes res[lastIndex] = createTextVNode(last.text + c.text) } else { //default key for nested array children (likely generated by v-for) if ( isTrue(children._isVList) && isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex) ) { c.key = `__vlist ${nestedIndex} _ ${i} __` } res.push(c) } } } return res } Copy code

normalizeArrayChildren
Receive 2 parameters,
children
Indicates the child node to be standardized,
nestedIndex
Represents a nested index.
normalizeArrayChildren
The main logic is to traverse
children
, Get a single node
c
, And right
c
Type judgment, if it is an array type, call recursively
normalizeArrayChildren
; If it is a basic type, pass
createTextVNode
Method is converted to
VNode
Type; otherwise it is already
VNode
Type again.

After right

children
Standardization,
children
Becomes a type
VNode
of
Array
.

4.2, the creation of VNode

_createElement
Another important process is
VNode
To create, first look at the source code:

let vnode, ns if ( typeof tag === 'string' ) { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { //platform built-in elements if ( process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) ) { warn( `The .native modifier for v-on is only valid on components but it was used on < ${tag} >.` , context ) } vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined , undefined , context ) } else if ( (!data || !data.pre) && isDef((Ctor = resolveAsset(context.$options, 'components' , tag))) ) { //component vnode = createComponent(Ctor, data, context, children, tag) } else { //unknown or unlisted namespaced elements //check at runtime because it may get assigned a namespace when its //parent normalizes children vnode = new VNode(tag, data, children, undefined , undefined , context) } } else { //direct component options/constructor vnode = createComponent(tag, data, context, children) } Copy code

Right here first

tag
Make a judgment if it is
string
Type, then determine if it is some built-in nodes, then directly create an ordinary
VNode
, If it is a registered component name, pass
createComponent
Create a component type
VNode
, Otherwise create an unknown label
VNode
. in case
tag
Is an
Component
Type, call directly
createComponent
Create a component type
VNode
node.

Well, here we know

vm._render
How to create a
VNode
, The next step is to put
VNode
Rendered into a real
DOM
And render it out, this process is through
vm._update
Finished, let s introduce it below
vm._update
Realization of

5. update

Vue
of
_update
It is a private method of the instance, it is called two times, one is the first rendering, and the other is when the data is updated; this article only analyzes the first rendering part.
_update
The role of the method is to
VNode
Rendered as real
DOM
. It is defined in
src/core/instance/liefcycle.js
In the file:

Vue.prototype._update = function ( vnode: VNode, hydrating?: boolean ) { const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode //Vue.prototype.__patch__ is injected in entry points //based on the rendering backend used. if (!prevVnode) { //initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */ ) } else { //updates vm.$el = vm.__patch__(prevVnode, vnode) } //... } Copy code

The core is to call

vm.__patch__
Method. This method has different definitions on different platforms. The definition in the web platform is as follows:

Vue.prototype .__ patch__ = inBrowser patch:? Noop copy the code

So in the browser rendering, it points to

patch
Method, it is defined in
src/platforms/web/runtime/patch.js
In the file:

Export const Patch: Function = createPatchFunction ({nodeOps, modules}) copy the code

The definition of the method is to call

createPatchFunction
The return value of the method,
createPatchFunction
Finally returned a
patch
Method is assigned to
vm.__patch__
. Take a look below
createPatchFunction
The realization of the method:

export function createPatchFunction ( backend ) { let i, j const cbs = {} const {modules, nodeOps} = backend for (i = 0 ; i <hooks.length; ++i) { cbs[hooks[i]] = [] for (j = 0 ; j <modules.length; ++j) { if (isDef(modules[j][hooks[i]])) { cbs[hooks[i]].push(modules[j][hooks[i]]) } } } //... return function patch ( oldVnode, vnode, hydrating, removeOnly ) { if (isUndef(vnode)) { if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false const insertedVnodeQueue = [] if (isUndef(oldVnode)) { //empty mount (likely as component), create new root element isInitialPatch = true createElm(vnode, insertedVnodeQueue) } else { const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { //patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null , null , removeOnly) } else { if (isRealElement) { //mounting to a real element //check if this is server-rendered content and if we can perform //a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute( SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } 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) } //replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) //create new node 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. //eg 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([oldVnode], 0 , 0 ) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm } } Copy code

Back to

patch
The method itself, it receives 4 parameters,
oldVnode
Means old
VNode
Node, it can also be non-existent or a
DOM
Object
vnode
Means execution
_render
After returning
VNode
Node
hydrating
Indicates whether it is server-side rendering;
removeOnly
Is for
transition-group
use.

The following uses an example to analyze its execution logic.

var app = new Vue({ el : '#app' , render : function ( createElement ) { return createElement( 'div' , { attrs : { id : 'app' } }, this .message) }, data : { message : 'Hello Vue!' }, }) Copy code

in

vm._update
The method is called like this
patch
Method of:

Initial the render// vm. $ EL Patch __ .__ = vm (vm. $ EL, the vnode, Hydrating, false /* removeonly */ ) Copy the code

In execution

patch
Function, the incoming
vm.$el
Corresponding to the example
id
for
app
of
DOM
Object, this is
index.html
Written in the template
<div id="app">
,
vm.$el
The assignment is before
mountComponent
Function does,
vnode
Corresponds to calling
render
The return value of the function,
hydrating
In the case of non-server rendering
false
,
removeOnly
for
false
.

Then back to

patch
The execution process of the function, look at a few key steps:

const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) { //patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null , null , removeOnly) } else { if (isRealElement) { //mounting to a real element //check if this is server-rendered content and if we can perform //a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute( SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR) hydrating = true } 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) } //replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) //create new node 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) ) } Copy code

Due to our incoming

oldVnode
Is actually a
DOM container
,and so
isRealElement
for
true
, And then passed
emptyNodeAt
Method
oldVnode
Convert to
VNode
Object before calling
createElm
method. inside
createElm
It is very important here, let's take a look at its implementation:

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 if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } const data = vnode.data const children = vnode.children const tag = vnode.tag if (isDef(tag)) { 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__) { //... } else { createChildren(vnode, children, insertedVnodeQueue) if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } insert(parentElm, vnode.elm, refElm) } if (process.env.NODE_ENV !== 'production' && data && data.pre) { creatingElmInVPre-- } } else if (isTrue(vnode.isComment)) { vnode.elm = nodeOps.createComment(vnode.text) insert(parentElm, vnode.elm, refElm) } else { vnode.elm = nodeOps.createTextNode(vnode.text) insert(parentElm, vnode.elm, refElm) } } Copy code

createElm
The role of the virtual node is to create a real
DOM
And insert it into its parent node. Let s take a look at some of its key logic,
createComponent
The purpose of the method is to try to create a child component, in the current
case
Its return value is
false
; Next judge
vnode
Does it contain
tag
, If it contains, simply right
tag
The legality of is checked in a non-production environment to see if it is a legal label; then go to the platform
DOM
The operation to create a placeholder element.

vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode) setScope(vnode) Copy code

Next call

createChildren
Method to create child elements:

createChildren(vnode, children, insertedVnodeQueue) function createChildren ( vnode, children, insertedVnodeQueue ) { if ( Array .isArray(children)) { if (process.env.NODE_ENV !== 'production' ) { checkDuplicateKeys(children) } for ( let 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 code

createChildren
The logic is very simple, in fact it is to traverse the child virtual nodes and call recursively
createElm
, This is a commonly used depth-first traversal algorithm. One thing to note here is that during the traversal process, the
vnode.elm
As the parent container
DOM
The node placeholder is passed in.

Then call

invokeCreateHooks
Method executes all
create
Hook and put
vnode
push
To
insertedVnodeQueue
in.

if (isDef(data)) { invokeCreateHooks(vnode, insertedVnodeQueue) } function invokeCreateHooks ( vnode, insertedVnodeQueue ) { for ( let i = 0 ; i <cbs.create.length; ++i) { cbs.create[i](emptyNode, vnode) } i = vnode.data.hook //Reuse variable if (isDef(i)) { if (isDef(i.create)) i.create(emptyNode, vnode) if (isDef(i.insert)) insertedVnodeQueue.push(vnode ) } } Copy code

Last call

insert
Method
DOM
Insert into the parent node, because it is a recursive call, the child element will be called first
insert
, So the whole
vnode
The insertion order of tree nodes is first child then parent.

insert(parentElm, vnode.elm, refElm) function insert ( parent, elm, ref ) { if (isDef(parent)) { if (isDef(ref)) { if (nodeOps.parentNode(ref) === parent) { nodeOps.insertBefore(parent, elm, ref) } } else { nodeOps.appendChild(parent, elm) } } } Copy code

insert
The logic is very simple, call some
nodeOps
Insert the child node into the parent node, the helper method is defined in
src/platforms/web/runtime/node-ops.js
In the file:

export function insertBefore ( parentNode: Node, newNode: Node, referenceNode: Node ) { parentNode.insertBefore(newNode, referenceNode) } export function appendChild ( node: Node, child: Node ) { node.appendChild(child) } Copy code

in

createElm
In the process, if
vnode
Node does not contain
tag
, It may be a comment or a plain text node, which can be inserted directly into the parent element.

Back again

patch
Method, called for the first rendering
createElm
Method, passed in here
parentElm
Yes
oldVnode.elm
The parent element. In fact, the whole process is to recursively create a complete
DOM
Tree and insert into
Body
in.

Finally, according to the previous recursion

createElm
Generated
vnode
Insert the sequential queue, execute related
insert
Hook function.

6. summary

So far, how to render the template and data from the main line into the final

DOM
The analysis of the process is complete, the following figure can be more intuitive to see the initialization
Vue
The whole process to the final rendering.

This article only analyzes the most basic and simplest scenarios. In practice, the page will be divided into many components, so the process of componentization will be analyzed in the next article.