In one sentence:
The render function is responsible for parsing the template into Vnode (virtual DOM) nodes
How to do it?
1. What is the output result of template compilation?
Template compilation will translate the template written by the user ↓
<div id="NLRX"><p>Hello {{name}}</p></div>
Convert to ast expressed in hierarchical objects
ast = { 'type': 1, 'tag': 'div', 'attrsList': [ { 'name':'id', 'value':'NLRX', } ], 'attrsMap': { 'id': 'NLRX', }, 'static':false, 'parent': undefined, 'plain': false, 'children': [{ 'type': 1, 'tag': 'p', 'plain': false, 'static':false, 'children': [ { 'type': 2, 'expression': '"Hello "+_s(name)', 'text': 'Hello {{name}}', 'static':false, } ] }] }
Because the focus of this article is not template compilation, the conversion process here is omitted, but I recommend you
https://vue-js.com/learn-vue/complie/
Can answer your doubts.
The render function actually does two things
1. Generate render function from ast
with(this){ reurn _c( 'div', { attrs:{"id":"NLRX"}, } [ _c('p'), [ _v("Hello "+_s(name)) ] ]) }
Don't worry, function content (with(this) {...}) The formation of will be discussed later.
To illustrate, you may wonder why the render function should first
with(this){ //Function execution }
This is actually a special syntax. The function execution environment inside with will be switched to this_ c is clearly not passed into the function body as a parameter, but why can it be called? Because it's on this_ c method, and the internal environment of the function is this, so it can be called directly. What is this? It's actually vue instance vm!
2. Run render function to generate vnode node
Intermediate functions are called multiple times_ c. That is, create elemnt to generate vnode,
_ The function of c is to generate the corresponding vnode node according to the parameters entered by the user. The usage of createElement in the official api document is as follows. Whether it is very similar to the content in the render function above is actually the same thing.
PS: what does vnode look like? As follows, this is the final result of render
<div id='a'><span>Hard to cool and hot blood</span></div> // VNode node { tag:'div', data:{}, children:[ { tag:'span', text:'Hard to cool and hot blood' } ] }
First, how to generate the render function?
The predecessors have made a complete statement. Post it directly
https://vue-js.com/learn-vue/complie/codegen.html#_1 -Foreword
The second thing, how to execute the render function?
//Source location: Src / code / instance / render js function initRender (vm: Component) { vm._vnode = null // the root of the child tree vm._staticTrees = null // v-once cached trees const options = vm.$options const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree const renderContext = parentVnode && parentVnode.context vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject // bind the createElement fn to this instance // so that we get proper render context inside i t. // 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) //mark2: the user creates a vnode and integrates the string in rende r into a vnode template // 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) // $attrs & $listeners are exposed for easier HOC creation. // they need to be reactive so that HOCs using them are always updated const parentData = parentVnode && parentVnode.data /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { ... It doesn't matter here } }
In initRender, vue is defined for the instance vm of vue_ c function,
This is called in the render function_ c buried the foreshadowing.
But actually, the position of the render function is not really defined here, so where is the position of the render function really defined?
In render JS file, the scroll wheel continues to turn down, and we found this ↓
function renderMixin (Vue: Class<Component>) { ......Omit unimportant logic Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options // render self let vnode try { currentRenderingInstance = vm vnode = render.call(vm._renderProxy, vm.$createElement) //mark: from the with function to the real vnode,renderProxy doesn't know what it is, but it can provide_ c. Then C calls VM$ CreateElement //Parameter VM$ CreateElement is not required under normal circumstances, but not under special circumstances } catch (e) { ... } finally { ... } // 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)) { vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode } }
Observe vnode = render Call (VM. _renderproxy, VM. $createElement) is the place where the render function is called, and the vnode is generated. Finally, the vnode node is returned
After that, vnode can update the data of the page by using diff algorithm.
The process of executing render is to_ c. implementation and supplement_ c returns the vnode object
with(this){ reurn _c( 'div', { attrs:{"id":"NLRX"}, } [ _c('p'), [ _v("Hello "+_s(name)) ] ]) }
Well, thank you very much for seeing here. Finally, let's explain_ c, that is, createElement. I believe you can fully understand the whole render process!
The following is the source code of createElement:
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) }
The first layer createElement is just an intermediate layer, which is actually used to call_ Some intermediate processing is done for createElement
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> { if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 'Always create fresh vnode data objects in each render!', context ) return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // in case of component :is set to falsy value return createEmptyVNode() } // warn against non-primitive key if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.key) && !isPrimitive(data.key) ) { if (!__WEEX__ || !('@binding' in data.key)) { warn( 'Avoid using non-primitive value as key, ' + 'use string/number value instead.', context ) } } // support single function children as default scoped slot if (Array.isArray(children) && typeof children[0] === 'function' ) { data = data || {} data.scopedSlots = { default: children[0] } children.length = 0 } if (normalizationType === ALWAYS_NORMALIZE) { children = normalizeChildren(children) } else if (normalizationType === SIMPLE_NORMALIZE) { children = simpleNormalizeChildren(children) } 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() } }
And right_ CreateElement glances at it roughly, and finally return s the vnode node, which is_ The function of createElement is compared with the render function:
with(this){ reurn _c( 'div', { attrs:{"id":"NLRX"}, } [ _c('p'), [ _v("Hello "+_s(name)) ] ]) }
_ createElemen generates vnode s according to the environment (context, usually vm), tag (tag:div), data: attrs:{"id":"NLRX"}, and children (child nodes)