This article will analyze how vNode in vue is created by interpreting the source code of the render function. In the version, whether you write the render function directly, use the template or el attribute, or use the .vue single file, you need to compile it into the render function for vnode creation, and then render it into the real DOM. If you don't know much about the vue source code directory, it is recommended to read it first. In-depth vue -- source code directory and compilation process.
01 render function
The render method is defined in the file src/core/instance/
._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // ... // 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 { vnode = (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 (.NODE_ENV !== 'production' && vm.$) { try { vnode = vm.$(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } // if the returned array contains only a single node, allow it if ((vnode) && === 1) { vnode = vnode[0] } // return empty vnode in case the render function errored out if (!(vnode instanceof VNode)) { if (.NODE_ENV !== 'production' && (vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent = _parentVnode return vnode }
_render is defined on the prototype of vue and will return vnode. vnode is created through code (vm._renderProxy, vm.$createElement).
During the vnode creation process, if an error occurs, the code in the catch will be executed for downgrading.
The most core code in _render is:
vnode = (vm._renderProxy, vm.$createElement)
Next, analyze what are the render, vm._renderProxy, and vm.$createElement here.
render function
const { render, _parentVnode } = vm.$options
The render method is extracted from $options. There are two ways to get the render method:
The render function written directly by the developer in the component
Generate by compiling the template attribute
Parameter vm._renderProxy
vm._renderProxy is defined in src/core/instance/ , and is the first parameter of the call, specifying the context in which the render function is executed.
/* istanbul ignore else */ if (.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm }
Production environment:
vm._renderProxy = vm, that is, in production environment, the context of the execution of the render function is the current vue instance, that is, this of the current component.
Development environment:
The development environment will execute initProxy(vm), and initProxy is defined in the file src/core/instance/.
let initProxy // ... initProxy = function initProxy (vm) { if (hasProxy) { // determine which proxy handler to use const options = vm.$options const handlers = && ._withStripped ? getHandler : hasHandler vm._renderProxy = new Proxy(vm, handlers) } else { vm._renderProxy = vm } }
The definition of hasProxy is as follows
const hasProxy = typeof Proxy !== 'undefined' && isNative(Proxy)
Used to determine whether the browser supports es6 Proxy.
Proxy functions to intercept an object when accessing it. The first parameter of new Proxy represents the object to be intercepted, and the second parameter is the object used to customize the intercepting behavior.
Development environment, if Proxy is supported, it will intercept vm instances. Otherwise, it will be the same as the production environment, and directly assign vm tovm._renderProxy
. The specific interception behavior is specified by the handlers object.
When the render function is handwritten,handlers = hasHandler
, render function generated by template, handlers = getHandler. hasHandler code:
const hasHandler = { has (target, key) { const has = key in target const isAllowed = allowedGlobals(key) || (typeof key === 'string' && (0) === '_' && !(key in target.$data)) if (!has && !isAllowed) { if (key in target.$data) warnReservedPrefix(target, key) else warnNonPresent(target, key) } return has || !isAllowed } }
getHandler code
const getHandler = { get (target, key) { if (typeof key === 'string' && !(key in target)) { if (key in target.$data) warnReservedPrefix(target, key) else warnNonPresent(target, key) } return target[key] } }
hasHandler and getHandler intercept the read of the properties of the vm object and the operation of propKey in proxy, and verify the parameters of the vm, and then call warningNonPresent and warningReservedPrefix for Warn warning.
It can be seen that the main function of the initProxy method is to intercept and discover problems and throw errors during development, so that developers can modify problems in a timely manner.
parametervm.$createElement
vm.$createElement is the createElement function passed in when handwriting the render function. It is defined in the initRender method. The initRender is executed when new Vue is initialized, and the parameter is the instance vm.
export function initRender (vm: Component) { // ... // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) // ... }
From the comments in the code, we can see: vm.$createElement
It is a method provided for developers to write render functions by hand. vm._c is a method used for render functions generated by compiling template. They all call the createElement method.
02 createElement method
The createElement method is defined in the src/core/vdom/ file
const SIMPLE_NORMALIZE = 1 const ALWAYS_NORMALIZE = 2 // 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 ((data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } return _createElement(context, tag, data, children, normalizationType) }
The createElement method mainly processes the parameters and then calls the _createElement method to create a vnode.
Let’s take a look at the parameters that createElement can receive in the vue document.
// @returns {VNode} createElement( // {String | Object | Function} // An HTML tag string, component option object, or // parse an async asynchronous function of any of the above. Required parameters. 'div', // {Object} // A data object containing template-related attributes // You can use these features in template. Optional parameters. { }, // {String | Array} // Sub-virtual nodes (VNodes), built from `createElement()`, // You can also use strings to generate "text virtual nodes". Optional parameters. [ 'Write some words first', createElement('h1', 'A headline'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] )
Except for the first parameter that is a required parameter in the document, all other parameters are optional parameters. That is to say, when using the createElement method, you can pass the second parameter without passing it, but only the first parameter and the third parameter. The parameter processing I just mentioned is to deal with this situation.
if ((data) || isPrimitive(data)) { normalizationType = children children = data data = undefined }
By determining whether data is an array or a basic type, if this condition is met, it means that the parameter passed at this position is children, and then the parameters are reassigned in sequence. This method is called overloading.
Overload: The function name is the same, the function parameter list is different (including the number of parameters and parameter types), and the return type can be the same or different.
After processing the parameters, call the _createElement method to create a vnode. Below is the core code of the _createElement method.
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { // ... if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } let vnode, ns if (typeof tag === 'string') { let Ctor // ... if ((tag)) { // platform built-in elements vnode = new VNode( (tag), data, children, undefined, undefined, context ) } else if ((!data || !) && 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 ((vnode)) { return vnode } else if (isDef(vnode)) { if (isDef(ns)) applyNS(vnode, ns) if (isDef(data)) registerDeepBindings(data) return vnode } else { return createEmptyVNode() } }
The method will make judgments at the beginning. If the data is responsive data and the component's is attribute is not the true value, it will call the createEmptyVNode method to create an empty vnode. Next, according to the value of normalizationType, the normalizeChildren or simpleNormalizeChildren method is called to process the parameter children. These two methods are defined in the src/core/vdom/helpers/ file.
// 1. When the children contains components - because a functional component // may return an Array instead of a single root. In this case, just a simple // normalization is needed - if any child is an Array, we flatten the whole // thing with . It is guaranteed to be only 1-level deep // because functional components already normalize their own children. export function simpleNormalizeChildren (children: any) { for (let i = 0; i < ; i++) { if ((children[i])) { return ([], children) } } return children } // 2. When the children contains constructs that always generated nested Arrays, // . <template>, <slot>, v-for, or when the children is provided by user // with hand-written render functions / JSX. In such cases a full normalization // is needed to cater to all possible types of children values. export function normalizeChildren (children: any): ?Array<VNode> { return isPrimitive(children) ? [createTextVNode(children)] : (children) ? normalizeArrayChildren(children) : undefined }
The purpose of normalizeChildren and simpleNormalizeChildren is to flatten the children array and finally return a one-dimensional array of vnodes.
simpleNormalizeChildren is for functional components, so you only need to consider the case that children are two-dimensional arrays. The normalizeChildren method takes into account the case that children are multi-layer nested arrays. normalizeChildren will start to judge the type of children. If children are the basic type, create text vnode directly. If it is an array, call the normalizeArrayChildren method, and make recursive calls in the normalizeArrayChildren method, and finally convert children into one-dimensional array.
Next, continue to look at the _createElement method. If the type of the tag parameter is not a String type, it is a component, call createComponent to create a vnode. If the tag is String type, then determine whether the tag is a reserved tag of html and whether it is an unknown node. By calling new VNode() and passing in different parameters to create a vnode instance.
In either case, the vnode is created through the class VNode. The following is the source code of the VNode class, defined in the file src/core/vdom/
export default class VNode { tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // rendered in this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ?ComponentOptions; // for SSR caching devtoolsMeta: ?Object; // used to store functional render context for devtools fnScopeId: ?string; // functional scope id support constructor ( tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions, asyncFactory?: Function ) { = tag // Tag name = data // Current node data = children // Children nodes = text // text = elm // Corresponding real DOM node = undefined // Namespace = context // Current node context = undefined // Functional component context = undefined // Functional component configuration parameters = undefined // Functional component ScopeId = data && // Child node key attribute = componentOptions // Component configuration items = undefined // Component instance = undefined // Parent node = false // Is it a native HTML snippet or just plain text = false // Static node marking = true // Whether to insert as root node = false // Is it an annotation node? = false // Is it a cloned node? = false // Is there a v-once command = asyncFactory // Asynchronous factory method = undefined // Asynchronous Meta = false // Whether to occupy asynchronously } // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return } }
The data defined by the VNode class are used to describe VNode.
At this point, the source code of the render function to create vdom has been analyzed. Let’s briefly summarize and sort it out.
_render is defined on . The _render function executes the method render method. In the development environment, the vm instance will be proxyed to verify the correctness of the vm instance data. In the render function, the render parameter createElement method will be executed. CreateElement will process the parameters. After processing the parameters, call _createElement, and the _createElement method will eventually call new VNode() directly or indirectly to create a vnode instance.
03 vnode && vdom
The vnode returned by createElement is not a real dom element. The full name of VNode is "Virtual Node". The information it contains will tell what nodes and its children on the Vue page need to be rendered. What we often call "Virtual Dom" is the name of the entire VNode tree built by the Vue component tree.
04 Experience
When reading the source code, do not only look at the source code. It must be analyzed in combination with specific use, so that you can understand the intention of a certain piece of code more clearly. Just like the render function in this article, if you have never used the render function, it may be difficult to read this source code directly. You might as well read the document first, write a demo, see the specific use, and then analyze the source code in comparison. In this way, many more confusing problems will be solved.
Summarize
The above is the method of creating Virtual Dom in vue introduced to you by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support for my website!
If you think this article is helpful to you, please reprint it. Please indicate the source, thank you!