foreplay

Before we get to the Vue Render function, we need to understand the overall flow of vue (as shown above).

From the figure above, you should be able to understand how a Vue component works.

  • Templates are compiled to generate AST trees
  • The AST tree generates Vue’s render function
  • The render function combines data to generate a vNode(Virtual DOM Node) tree
  • New UI after Diff and Patch (real DOM rendering)

In this diagram, we need to understand the following concepts:

  • Template, Vue template is pure HTML, template syntax based on Vue, can be more convenient to deal with the relationship between data and UI interface
  • AST, which is short for Abstract Syntax Tree, Vue parses HTML template into AST, and performs some optimized markup processing on AST to extract the largest static Tree, so that Virtual DOM can directly skip the following Diff
  • The render function is used to generate the Virtual DOM. Vue recommends using templates to build our application, and in the underlying implementation Vue will eventually compile the templates into rendering functions. Therefore, if we want more control, we can write the render function directly.
  • Virtual DOM
  • Watcher, each Vue component has a correspondingwatcher, it will be in the componentrenderAnd when the dependency is updated, the component is triggered to re-render, and Vue automatically optimizes and updates the DOM as needed

In the figure above, the render function can be used as a dividing line:

  • renderThe left-hand side of the function is calledCompile timeTo convert the Vue board into a rendering function
  • renderOn the right side of the function is the Vue runtime, which mainly generates the Virtual DOM tree, Diff and Patch from the rendering function

render

Vue recommends using templates to create your HTML in most cases. However, in some scenarios, you really need the full programming power of JavaScript. In this case you can use render functions, which are closer to the compiler than templates.

Example of rendering titles

For example, here is an example of rendering titles on the official website

The implementation, you can look it up, I’m not going to go into detail here. The template implementation and render function implementation code are pasted here:

. Vue single file implementation

<template>
    <h1 v-if="level === 1">
        <slot></slot>
    </h1>
    <h2 v-else-if="level === 2">
        <slot></slot>
    </h2>
    <h3 v-else-if="level === 3">
        <slot></slot>
    </h3>
    <h4 v-else-if="level === 4">
        <slot></slot>
    </h4>
    <h5 v-else-if="level === 5">
        <slot></slot>
    </h5>
    <h6 v-else-if="level === 6">
        <slot></slot>
    </h6>
</template>
<script>
export default {
  name: 'anchored-heading'.props: {
        level: {
            type: Number.required: true}}}</script>
Copy the code

Implementation of the Render function

Vue.component('anchored-heading', {
    render: function (createElement) {
        return createElement(
            'h' + this.level,   // tag name Tag name
            this.$slots.default // Array in child component)},props: {
        level: {
            type: Number.required: true}}})Copy the code

Isn’t that neat?

Node & tree & Virtual DOM

Now that we know the basic concepts and rendering functions of Vue, we need to know a little bit about how the browser works. This is important for learning about the render function. For example, the following HTML code:

<div>
    <h1>My title</h1>
    Some text content
    <! --TODO: Add tagline -->
</div>
Copy the code

When the browser reads the code, it creates a DOM node tree to keep track of. If you were to draw a family tree to track the development of family members, the HTML DOM node tree might look something like this:

Every element and text is a node, and even comments are nodes. A node is part of a page, and like a family tree, each node can have child nodes.

It can be difficult to update all the nodes efficiently, but don’t worry, the Vue will do it for you automatically. You just need to tell the Vue what HTML is on the page.

It can be an HTML template, for example:

<h1>{{title}}</h1>
Copy the code

It can also be a render function:

render(h){
  return h('h1'.this.title)
}
Copy the code

In both cases, Vue automatically keeps the page updated if the title value changes.

Virtual DOM

After compiling the templates, the Vue compiler compiles them into render functions, which return a virtual DOM tree when called.

Once we have the virtual DOM tree, we hand it over to a Patch function, which takes care of rendering the virtual DOM into the real DOM. In this process, Vue’s own responsive system detects the data sources it relies on during the rendering process, and when it detects the data source, it can accurately detect changes in the data source so that it can re-render when needed. After re-rendering, a new tree will be generated, and the new tree will be compared with the old tree to obtain the final change point that needs to be modified to the real DOM. Finally, the change will be implemented through the Patch function.

In simple terms, Vue compiles templates into virtual DOM rendering functions on the underlying implementation of Vue. Combined with Vue’s built-in response system, Vue can intelligently calculate the minimum cost of re-rendering components when the state should change and apply it to DOM operations.

Vue allows us to pass a JavaScript Object as component data via the data argument. Vue iterates over data Object properties, sets the description Object using the Object.defineProperty method, and intercepts reading and modifying the property via gett/setter functions.

Vue creates a Watcher layer to record properties as dependencies during component rendering, and when setters for dependencies are called, Watcher is notified to recalculate so that its associated components can be updated.

For Virtual DOM, if you want to have a deeper understanding, you can look at the Virtual DOM of Vue principle parsing

From the previous study, we learned that Vue keeps track of changes to the real DOM by creating a ** virtual DOM”. For example,

return createElement('h1'.this.title)
Copy the code

CreateElement, createNodeDescription, returns a Virtual Node, often abbreviated as “VNode”. The virtual DOM is the collective name of the entire VNode tree built from the Vue component tree.

The entire VNode tree created by the Vue component tree is unique and non-repeatable. For example, the render function below is invalid.

render(createElement) {
  const vP = createElement('p'.'hello james')
  return createElement('div'[// Error, there are duplicate vNodes
    vP, vP
  ])
}
Copy the code

If you need a lot of duplicate components/elements, you can use factory functions. Such as:

render(createElement){
  return createElement('div'.Array.apply(null, {length: 20}).map((a)= > {
    return createElement('p'.'hi james')}}))Copy the code

Vue rendering mechanism

Here is a component rendering flow for a standalone build:

Two concepts of Vue are involved:

  • Independent build, including template compiler, rendering process: HTML string => Render function => vNode => real DOM
  • Build at runtime, no template compiler included, render process: Render function => vNode => real DOM

Packages built at runtime will have one less template compiler (and therefore run faster) than standalone packages. It is also different with the $mount function, which is the starting point in the rendering process, as illustrated by this flow chart:

As you can see from the image above, three templates are provided during the rendering process:

  • Customize the render function
  • template
  • el

All render pages, which corresponds to the three ways we write Vue. All three modes end up with the render function.

For normal development, using template and EL is friendlier and easier to understand, but less flexible. The Render function, which is capable of more complex logic, has high flexibility, but is relatively poor for users to understand.

Customize the render function

Vue.component('anchored-heading', {
    render(createElement) {
        return createElement (
            'h' + this.level,   
            this.$slots.default 
        )
    },
    props: {
        level: {
            type: Number.required: true}}})Copy the code

The template method

const app = new Vue({
    template: `<div>{{ msg }}</div>`,
    data () {
        return {
            msg: 'Hello Vue.js! '}}})Copy the code

El writing

let app = new Vue({
    el: '#app',
    data () {
        return {
            msg: 'Hello Vue! '}}})Copy the code

Understand & use the render function

createElement

CreateElement is a must when using the Render function.

CreateElement method parameter

CreateElement can accept multiple arguments

Parameter 1:{String | Object | Function }Will pass

The first argument is mandatory. It can be a String, an Object, or a Function

// String
Vue.component('custom-element', {
    render(createElement) {
        return createElement('div'.'hello world! ')}})// Object
Vue.component('custom-element', {
    render(createElement) {
        return createElement({
          template: `
      
hello world!
`
})}})// Function Vue.component('custom-element', { render(createElement) { const elFn = (a)= > { template: `
hello world!
`
} return createElement(elFn()) } }) Copy the code

The above code is equivalent to:

<template>
  <div>hello world!</>
</template>
<script>
  export default {
    name: 'custom-element'
  }
</script>
Copy the code

Parameter 2:{ Object }optional

The second parameter to createElemen is optional. This parameter is an Object, for example:

Vue.component('custom-element', {
  render(createElement) {
    const self = this;
    return createElement('div', {
      'class': {
        foo: true.bar: false
      },
      style: {
        color: 'red'.fontSize: '18px'
      },
      attrs: {
        ...self.attrs,
        id: 'id-demo'
      },
      on: {
        ...self.$listeners,
        click: (e) = > {console.log(e)}
      },
      domProps: {
        innerHTML: 'hello world! '
      },
      staticClass: 'wrapper'})}})Copy the code

Is equivalent to:

<template>
  <div :id="id" class="wrapper" :class="{'foo': true, 'bar': false}" :style="{color: 'red', fontSize: '18px'}" v-bind="$attrs" v-on="$listeners" @click="(e) => console.log(e)"> hello world! </div>
</template>
<script>
export default {
  name: 'custom-element',
  data(){
    return {
      id: 'id-demo'}}}</script>

<style>
.wrapper{
  display: block;
  width: 100%;
}
</style>
Copy the code

Parameter 3:{ String | Array }optional

The third parameter to createElement is optional and can be passed a String or Array, for example:

Vue.component('custom-element', {
    render (createElement) {
        var self = this
        return createElement(
            'div',
            {
                class: {
                    title: true
                },
                style: {
                    border: '1px solid'.padding: '10px'
                }
            }, 
            [
                createElement('h1'.'Hello Vue! '),
                createElement('p'.'Hello world! '])}})Copy the code

Is equivalent to:

<template>
  <div :class="{'title': true}" :style="{border: '1px solid', padding: '10px'}">
    <h1>Hello Vue!</h1>
    <p>Hello world!</p>
  </div>
</template>
<script>
export default {
  name: 'custom-element',
  data(){
    return {
      id: 'id-demo'}}}</script>
Copy the code

Create a component with the same effect using Template and Render

The template method

<template>
  <div id="wrapper" :class="{show: show}" @click="clickHandler">
    Hello Vue!
  </div>
</template>
<script>
export default {
  name: 'custom-element',
  data(){
    return {
      show: true}},methods: {
    clickHandler(){
      console.log('you had click me! '); }}}</script>
Copy the code

Render way

Vue.component('custom-element', {
      data () {
        return {
            show: true}},methods: {
          clickHandler: function(){
            console.log('you had click me! '); }},render: function (createElement) {
          return createElement('div', {
              class: {
                show: this.show
              },
              attrs: {
                id: 'wrapper'
              },
              on: {
                click: this.handleClick
              }
          }, 'Hello Vue! ')}})Copy the code

CreateElement parsing procedure

CreateElement method analytical flow chart (pick to: segmentfault.com/a/119000000…).

CreateElement method core source code parsing process (need to have a certain strength to JS, pick to: segmentfault.com/a/119000000…).

const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

function createElement (context, tag, data, children, normalizationType, alwaysNormalize) {

    // Data is not transmitted
    if (Array.isArray(data) || isPrimitive(data)) {
        normalizationType = children
        children = data
        data = undefined
    }

    // If alwaysNormalize is true
    // normalizationType should be set to the constant value of ALWAYS_NORMALIZE
    if (alwaysNormalize) normalizationType = ALWAYS_NORMALIZE
        Call _createElement to create a virtual node
        return _createElement(context, tag, data, children, normalizationType)
    }

    function _createElement (context, tag, data, children, normalizationType) {
        /** * If data.__ob__ is present, data is observed by the Observer * cannot be used as virtual node data * needs to throw a warning and return an empty node ** Monitored data cannot be used as vNode rendered data for the following reasons: * Data may be changed during vNode rendering, which triggers monitoring and results in undesired operations */
        if(data && data.__ob__) { process.env.NODE_ENV ! = ='production' && warn(
            `Avoid using observed data object as vnode data: The ${JSON.stringify(data)}\n` +
            'Always create fresh vnode data objects in each render! ',
            context
            )
            return createEmptyVNode()
        }

        // When the component's IS property is set to a falsy value
        // Vue will not know what to render this component into
        // Render an empty node
        if(! tag) {return createEmptyVNode()
        }

        // Scope slot
        if (Array.isArray(children) && typeof children[0= = ='function') {
            data = data || {}
            data.scopedSlots = { default: children[0] }
            children.length = 0
        }

        // Depending on the value of normalizationType, select different processing methods
        if (normalizationType === ALWAYS_NORMALIZE) {
            children = normalizeChildren(children)
        } else if (normalizationType === SIMPLE_NORMALIZE) {
            children = simpleNormalizeChildren(children)
        }
        let vnode, ns

        // If the label name is a string
        if (typeof tag === 'string') {
            let Ctor
            // Get the namespace of the tag name
            ns = config.getTagNamespace(tag)

            // Check whether the label is reserved
            if (config.isReservedTag(tag)) {
                // If the label is reserved, create such a vnode
                vnode = new VNode(
                    config.parsePlatformTagName(tag), data, children,
                    undefined.undefined, context
                )

                // If the label is not reserved, then we will try to find the definition of the label from the COMPONENTS of the VM
            } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
                // If the label definition is found, the virtual component node is created with it
                vnode = createComponent(Ctor, data, context, children, tag)
            } else {
                // Create a vnode
                vnode = new VNode(
                    tag, data, children,
                    undefined.undefined, context
                )
            }

        When a tag is not a string, we consider it to be the component's constructor class
        // Create it directly
        } else {
            vnode = createComponent(tag, data, context, children)
        }

        // If there are vNodes
        if (vnode) {
            // If there is a namespace, apply the namespace and return vNode
            if (ns) applyNS(vnode, ns)
            return vnode
        // Otherwise, return an empty node
        } else {
            return createEmptyVNode()
        }
    }
}

Copy the code

Use the Render function instead of the template function

When using Vue templates, we can flexibly use template syntax such as V-if, V-for, V-model, and

in templates. However, there is no dedicated API provided in the Render function. If you use this in Render, you need to use native JavaScript to do it.

v-if & v-for

<ul v-if="items.length">
    <li v-for="item in items">{{ item }}</li>
</ul>
<p v-else>No items found.</p>
Copy the code

Render function implementation

Vue.component('item-list', {props: ['items'],
    render (createElement) {
        if (this.items.length) {
            return createElement('ul'.this.items.map((item) = > {
                return createElement('item')}}))else {
            return createElement('p'.'No items found.')}}})Copy the code

v-model

<template>
  <el-input :name="name" @input="val => name = val"></el-input>
</template>
<script>
export default {
  name: 'app',
  data(){
    return {
      name: 'hello vue.js'}}}</script>
Copy the code

Render function implementation

Vue.component('app', {
    data(){
      return {
        name: 'hello vue.js'}},render: function (createElement) {
        var self = this
        return createElement('el-input', {
            domProps: {
                value: self.name
            },
            on: {
                input: function (event) {
                    self.$emit('input', event.target.value)
                }
            }
        })
    },
    props: {
        name: String}})Copy the code

slot

In Vue, you can do this by:

  • this.$slotsGets the static content of the VNodes list.
render(h){
  return h('div'.this.$slots.default)
}
Copy the code

Is equivalent to:

<template> 
  <div>
    <slot> </slot>
  </div>
</template>
Copy the code

In Vue, you can do this by:

  • this.$scopedSlotsGets the scope slot that can be used as a function that returns VNodes
props: ['message'],
render (createElement) {
    // `<div><slot :text="message"></slot></div>`
    return createElement('div'[this.$scopedSlots.default({
            text: this.message
        })
    ])
}
Copy the code

To pass a scope slot to a child component using a render function, you can use the scopedSlots field in the VNode data:

<div id="app">
    <custom-ele></custom-ele>
</div>
Copy the code

Vue.component('custom-ele', {
    render: function (createElement) {
        return createElement('div', [
            createElement('child', {
                scopedSlots: {
                    default: function (props) {
                        return [
                            createElement('span'.'From Parent Component'),
                            createElement('span', props.text)
                        ]
                    }
                }
            })
        ])
    }
})

Vue.component('child', {
    render: function (createElement) {
        return createElement('strong'.this.$scopedSlots.default({
            text: 'This is Child Component'}}})))let app = new Vue({
    el: '#app'
}
Copy the code

JSX

If you’re used to template writing and then have to use the render function, it’s going to be pretty uncomfortable, especially with complex components. But using JSX in Vue brings us back to a more template-like syntax.

import View from './View.vue'

new Vue({
    el: '#demo',
    render (h) {
        return (
            <View level={1}>
                <span>Hello</span> world!
            </View>)}}Copy the code

Using H as an alias for createElement is a common convention in the Vue ecosystem, and is actually required by JSX to trigger an error in the application if h is disabled in scope.

conclusion

The key steps in Vue rendering are:

  • new VueTo perform initialization
  • mount$mount, through customizationrenderMethod,template.elSuch as generationrenderRendering function
  • throughWatcherListen for changes in data
  • When the data changes,renderThe function executes to generate a VNode object
  • throughpatchMethod to compare the old and new VNode objects throughDOM DiffAlgorithms to add/modify/remove real DOM elements

At this point, the entire New Vue rendering process is complete.

A link to the

  • Vue-jsx – Render title example
  • The DOM node tree
  • Virtual DOM of Vue principle analysis
  • vue-render-jameszhang