Principle analysis of Render function in vue

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)

Keywords: Vue

Added by dta on Thu, 03 Feb 2022 10:03:42 +0200