Render function

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.

Nodes, trees, and virtual DOM

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

Each element is a node. Each paragraph of text is also a node. Even comments are nodes. A node is a part of a page. Just like a family tree, each node can have child nodes (that is, each part can contain other parts).

Updating all of these nodes efficiently can be difficult, but fortunately you don’t have to do this manually. You just need to tell Vue what HTML you want on the page, which can be in a template:

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

Or in a render function:

render: function (createElement) {
  return createElement('h1'.this.blogTitle)
}
Copy the code

In both cases, Vue automatically keeps the page updated even if the blogTitle changes.

Virtual DOM

Vue tracks how it changes the real DOM by creating a virtual DOM. Take a closer look at this line of code:

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

What exactly does createElement return? It’s not an actual DOM element. It might be more accurately called createNodeDescription, because it contains information that tells the Vue what nodes need to be rendered on the page, including descriptions of their children. We describe such a node as a “virtual node”, often abbreviated to “VNode”. “Virtual DOM” is the name we use for the entire VNode tree built from the Vue component tree.

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // An HTML tag name, component option object, or
  Resolve an async function of any of the above. Required fields.
  'div'.// {Object}
  // A data object corresponding to the attribute in the template. Optional.
  {
    // (see next section for details)
  },

  // {String | Array}
  // Child virtual nodes (VNodes), built from 'createElement()',
  // You can also use strings to generate "text virtual nodes". Optional.
  [
    'Write some words first.',
    createElement('h1'.'A headline'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'}})])Copy the code

2.1 Dive into data objects

In rendering functions, some template attributes have top-level fields in the Vnode data object, which also allows binding of ordinary attributes as well as DOM properties such as innerHTML (which overrides the V-HTML directive).

{
  // Same API as' v-bind:class ',
  // Takes a string, object, or array of strings and objects
  'class': {
    foo: true.bar: false
  },
  // Same API as' v-bind:style ',
  // Accepts a string, object, or array of objects
  style: {
    color: 'red'.fontSize: '14px'
  },
  // Plain HTML attribute
  attrs: {
    id: 'foo'
  },
  / / component prop
  props: {
    myProp: 'bar'
  },
  // DOM property
  domProps: {
    innerHTML: 'baz'
  },
  // The event listener is in 'on',
  // Modifiers such as' V-on :keyup.enter 'are no longer supported.
  // keyCode needs to be checked manually in the handler.
  on: {
    click: this.clickHandler
  },
  // Only for components, for listening on native events, not for internal component use
  // 'vm.$emit' triggers the event.
  nativeOn: {
    click: this.nativeClickHandler
  },
  // Custom instruction. Note that you cannot bind 'oldValue' in 'binding'
  // Assign, because Vue has automatically synchronized for you.
  directives: [{name: 'my-custom-directive'.value: '2'.expression: '1 + 1'.arg: 'foo'.modifiers: {
        bar: true}}].// The format of the scope slot is
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props= > createElement('span', props.text)
  },
  // If the component is a child of another component, specify a name for the slot
  slot: 'name-of-slot'.// Other special top-level properties
  key: 'myKey'.ref: 'myRef'.// If you apply the same ref name to multiple elements in a render function,
  // then '$refs.myRef' becomes an array.
  refInFor: true
}
Copy the code

2.2 Complete Examples

Codesandbox. IO/s/pensive – b…

2.3 the constraint

The VNode must be unique

All VNodes in the component tree must be unique. This means that the following render functions are not valid:

render: function (createElement) {
  var myParagraphVNode = createElement('p'.'hi')
  return createElement('div'[// error - Duplicate VNode
    myParagraphVNode, myParagraphVNode
  ])
}
Copy the code

If you really need to repeat elements/components a lot, you can use factory functions to do it. For example, the following render function renders 20 identical paragraphs in a perfectly legal way:

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

Third, inRenderThe template function in the function

3.1 v-ifv-for

Vue’s rendering functions do not provide proprietary alternatives to what can easily be done in native JavaScript. For example, v-if and v-for are used in templates:

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

These can be overridden in the render function using JavaScript’s if/else and map:

props: ['items'].render: function (createElement) {
  if (this.items.length) {
    return createElement('ul'.this.items.map(function (item) {
      return createElement('li', item.name)
    }))
  } else {
    return createElement('p'.'No items found.')}}Copy the code

3.2 v-model

There is no direct counterpart to the V-Model in the rendering function — you have to implement the logic yourself:

props: ['value'].render: function (createElement) {
  var self = this
  return createElement('input', {
    domProps: {
      value: self.value
    },
    on: {
      input: function (event) {
        self.$emit('input', event.target.value)
      }
    }
  })
}
Copy the code

That’s the price of going deep, but it gives you a lot more control over the interaction details than a V-Model.

3.3 Event & key modifier

For.passive,.capture, and.once event modifiers, Vue provides prefixes that can be used with on:

The modifier The prefix
.passive &
.capture !
.once ~
.capture.once.once.capture ~!

Such as:

on: {
  '! click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~! mouseover': this.doThisOnceInCapturingMode
}
Copy the code

For all other modifiers, private prefixes are not required, because you can use event methods in event handlers:

The modifier Handle equivalent operations in functions
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if (event.target ! == event.currentTarget) return
Keys:.enter.13. if (event.keyCode ! == 13) returnFor other key modifiers, it can be13Instead ofAnother key code)
Modifier keys:.ctrl..alt..shift..meta if (! event.ctrlKey) return(will bectrlKeyChange toaltKey,shiftKeyormetaKey)

Here is an example using all modifiers:

on: {
  keyup: function (event) {
    // If the element that triggers the event is not an element of the event binding
    / / is returned
    if(event.target ! == event.currentTarget)return
    // If you press not Enter or
    // Did not press shift at the same time
    / / is returned
    if(! event.shiftKey || event.keyCode ! = =13) return
    // Prevent events from bubbling
    event.stopPropagation()
    // Block the element's default keyup event
    event.preventDefault()
    // ...}}Copy the code

3.4 slot

You can access the contents of static slots via this.$slots, where each slot is an array of vNodes:

render: function (createElement) {
  // `<div><slot></slot></div>`
  return createElement('div'.this.$slots.default)
}
Copy the code

You can also access the scope slots via this.$scopedSlots. Each scope slot is a function that returns a number of VNodes:

props: ['message'].render: function (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 object:

render: function (createElement) {
  // `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
  return createElement('div', [
    createElement('child', {
      // Pass 'scopedSlots' in the data object
      / / format for {name: props = > VNode | Array < VNode >}
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}
Copy the code

3.5 example

import { CreateElement, RenderContext } from 'vue/types/umd'

export default {
  functional: true.props: {
    row: Object.render: Function.index: Number.column: {
      type: Object.default: null}},render: (h: CreateElement, ctx: RenderContext) = > {
    const params: any = {
      row: ctx.props.row,
      index: ctx.props.index
    }
    if (ctx.props.column) params.column = ctx.props.column
    return ctx.props.render(h, params)
  }
}
Copy the code