Introduction to Vue.js Render Functions

Joshua Bemenderfer

Vue.js templates are incredibly powerful, and can accomplish almost everything you’d ever need in an app. However, there are a few use-cases, often those involving dynamic component creation based on input or slot values that are better served by render functions.

Those coming from a React world are probably very familiar with render functions. React components are built with them, usually through JSX. And while Vue render functions can also be written with JSX, we’re going to stick with raw JS so you can more easily understand the underpinnings of Vue’s component system.

It's worth noting that Vue.js' templates actually compile down to render functions at build time. Templates just provide a convenient and familiar syntax sugar on top of render functions. While more powerful, render functions often suffer in the readability department.

Creating a Component

Components with render functions do not have a template tag or property. Instead they define a function called render that receives a createElement(renderElement: String | Component, definition: Object, children: String | Array) argument (commonly aliased as h, for some reason, blame JSX) and returns an element created with that function. Everything else stays the same.

ExampleComponent.vue

export default {
  data() {
    return {
      isRed: true
    }
  },

  /*
   * Same as
   * <template>
   *   <div :class="{'is-red': isRed}">
   *     <p>Example Text</p>
   *   </div>
   * </template>
   */
  render(h) {
    return h('div', {
      'class': {
        'is-red': this.isRed
      }
    }, [
      h('p', 'Example Text')
    ])
  }
}

Replacing Shorthand Directives

Vue templates come with a variety of convenient features in order to add basic logic and binding features to templates. Render functions do not have access to these. Instead, they must be implemented in plain Javascript, which, for most directives, is fairly simple.

v-if

This one is easy. Instead of using v-if, just use a normal Javascript if (expr) statement around your createElement calls.

v-for

v-for can be implemented with any of the many Javascript iteration methods, for, for-of, Array.map, Array.filter, etc.. You can combine these in very interesting ways to implement filtering or state slicing without the need for computed properties.

For example, you could replace

<template>
  <ul>
    <li v-for="pea of pod">
      
    </li>
  </ul>
</template>

with

render(h) {
  return h('ul', this.pod.map(pea => h('li', pea.name)));
}

v-model

A good thing to keep in mind is that v-model is simply shorthand for a binding property to value and setting the data property whenever the input event is fired. Unfortunately, there’s no such shorthand for render functions. You have to implement it yourself, as shown below.

render(h) {
  return h('input', {
    domProps: {
      value: this.myBoundProperty
    },
    on: {
      input: e => {
        this.myBoundProperty = e.target.value
      }
    }
  })
}

Which is equivalent to:

<template>
  <input :value="myBoundProperty" @input="myBoundProperty = $event.target.value"/>
</template>

or

<template>
  <input v-model="myBoundProperty"/>
</template>

v-bind

Attribute and property bindings are placed in the element definition, as arttrs, props, and domProps (stuff like value and innerHTML).

render(h) {
  return h('div', {
    attrs: {
      // <div :id="myCustomId">
      id: this.myCustomId
    },

    props: {
      // <div :someProp="someonePutSomethingHere">
      someProp: this.someonePutSomethingHere
    },

    domProps: {
       // <div :value="somethingElse">
      value: this.somethingElse
    }
  });
}

As a side note, class and style bindings are handled directly at the root of the definition, not as attrs, props, or domProps.

render(h) {
  return h('div', {
    // "class" is a reserved keyword in JS, so you have to quote it.
    'class': {
      myClass: true,
      theirClass: false
    },

    style: {
      backgroundColor: 'green'
    }
  });
}

v-on

Event handlers are also added to the element definition directly, in the on (or nativeOn, which has the same effect as v-on.native for components.)

render(h) {
  return h('div', {
    on: {
      click(e) {
        console.log('I got clickeded!')
      }
    }
  });
}

The modifiers can be implemented inside the handler:

  • .stop -> e.stopPropagation()
  • .prevent -> e.preventDefault()
  • .self -> if (e.target !== e.currentTarget) return

Keyboard modifiers

  • .[TARGET_KEY_CODE] -> if (event.keyCode !== TARGET_KEY_CODE) return
  • .[MODIFIER] -> if (!event.MODIFIERKey) return

Special properties

Slots can be accessed through this.$slots as an array of createElement() nodes.

Scoped slots are stored in this.$scopedSlots[scope](props: object) as functions that return an array of createElement() nodes.

Enjoy the new, unlimited power granted to you by render functions! Just be careful to use wisely.

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...