Chapter 4. Render Functions and JSX

You’ve already seen how to use the template attribute to set the HTML of a component, and you’ve also seen how to use vue-loader to write your component HTML in a <template> tag. However, using templates isn’t the only way to indicate what Vue should display on the page: you can choose to use a render function instead. This also enables you to write JSX in your Vue application, which if you’re from a React background, you may be more comfortable with (although I still recommend giving templates a try!).

Note

You can also use the template attribute in your main Vue instance instead of having Vue use the HTML in your Vue element. It’ll be automatically added to the element you specify as the Vue element.

When you pass a function to the render property of your Vue instance, the function is passed a createElement function, which you can use to indicate the HTML that should be output to the page. As a simple example, the following renders <h1>Hello world!</h1> to the page:

new Vue({
  el: '#app',
  render(createElement) {
    return createElement('h1', 'Hello world!');
  }
});

createElement takes three arguments: the tag name of the element to be generated, an object containing options (such as HTML attributes, properties, event listeners, and class and style bindings), and either a child node or an array of child nodes. The tag name is required, and the other two are optional (and if you don’t specify the attributes object, you can specify the children as the second argument). Let’s look at each of those individually.

The Tag Name

The tag name is the simplest argument and the only required one. It can be either a string, or a function that returns a string. In the previous example, we’re returning h1, so an <h1> element will be created. Render functions also have access to this, so you can set the tag name from a property of the data object, prop, computed property, or anything like that:

new Vue({
  el: '#app',
  render(createElement) {
    return createElement(this.tagName, 'Hello world');
  },
  data: {
    tagName: 'h1'
  }
});

This capability is a big advantage of render functions over templates, in which it isn’t easy or readable to set a tag name dynamically. <{{ tagName }}> is invalid (and still not that easy to read!).

The Data Object

The data object is where you specify attributes that will affect the component or element. If you were writing a template, that’s everything that goes between the tag name and the closing >. For example, with <custom-button type="submit" v-bind:text="buttonText">, the attributes would be type="submit" v-bind:text="buttonText".

In this example, type is a normal HTML attribute being passed through to the component, and text is a component prop bound to the buttonText variable. For the same example using createElement, you could write the following:

new Vue({
  el: '#app',
  render(createElement) {
    return createElement('custom-button', {
      attrs: {
        type: 'submit'
      },
      props: {
        text: this.buttonText
      }
    });
  }
});

You’ll notice that we’re not using v-bind at all anymore; this is because we can refer to the variable directly as this.buttonText. Because this.buttonText is a dependency of the function, the render function will be called again whenever buttonText is updated and the DOM updated automatically, the same as with templates.

See Example 4-1 for all the options required to do everything you’ve learned so far in this book.

Example 4-1. The attributes of the options object
{
  // HTML attributes
  attrs: {
    type: 'submit'
  },

  // Props to be passed to components
  props: {
    text: 'Click me!'
  },

  // DOM properties such as innerHTML (instead of v-html)
  domProps: {
    innerHTML: 'Some HTML'
  },

  // Event listeners
  on: {
    click: this.handleClick
  },

  // The same as slot="exampleSlot" - used when the component is a child of
  // another component
  slot: 'exampleSlot',

  // The same as key="exampleKey" - used for components generated in a loop
  key: 'exampleKey',

  // The same as ref="exampleRef"
  ref: 'exampleRef',

  // The same as v-bind:class="['example-class'...
  class: ['example-class', { 'conditional-class': true }],

  // The same as v-bind:style="{ backgroundColor: 'red' }"
  style: { backgroundColor: 'red' }
}

Note that class and style are specified separately from the attrs property. This is because of the v-bind helpers; if you just specify the class or styles as a property of the attrs object, you won’t be able to specify classes as an array or object, or your styles as an object.

There are a couple of other properties for things that haven’t been covered in this book: check out the official documentation for a full list.

Children

The third and final argument is where you specify the children of the element. This can either be an array or a string. If it’s a string, the specified string will be rendered as the text of the element; and if it’s an array, you can call createElement again inside the array to generate a complicated tree.

Note

If you’re specifying children but not the data object, you can pass this as the second argument instead of the third.

Let’s take the following template from a previous section:

<div>
  <button v-on:click="counter++">Click to increase counter</button>
  <p>You've clicked the button {{ counter }}</p> times.
</div>

To write the same template by using createElement, you’d write the following:

  render(createElement) {
    return createElement(
      'div',
      [
        createElement(
          'button',
          {
            on: {
              click: () => this.counter++,
            }
          },
          'Click to increase counter'
        ),
        createElement(
          'p',
          `You've clicked the button ${this.counter} times`
        )
      ]
    );
  }

JSX

The previous example seems like a lot of code to do what we did in a four-line template. Luckily, with the help of babel-plugin-transform-vue-jsx, you can write your render function by using JSX along with the Babel plug-in to compile the JSX into createElement calls that Vue understands. Note that internally (and throughout the JSX ecosystem), the createElement function is usually aliased to a shorter name, h, but you don’t normally need to know that.

I won’t cover how to install babel-plugin-transform-vue-jsx here; check out the documentation for the plug-in to see how to do that.

After the Babel plug-in has been installed, the previous code can be rewritten like so:

  render() {
    return (
      <div>
        <button onClick={this.clickHandler}>Click to increase counter</button>
        <p>You've clicked the button {counter} times</p>
      </div>
    );
  }

That’s much better. A couple of other nice features of JSX work with Vue too.

In addition to being able to import and use components the same way you can in templates—by specifying the name of the component as the tag name—it’s also possible to import components the React way too. If the variable the component is imported with begins with a capital letter, you can use it without having to put it in the components object or using Vue.component(). For example:

import MyComponent from './components/MyComponent.vue';

new Vue({
  el: '#app',
  render() {
    return (
      <MyComponent />
    );
  }
});

JSX spread is also supported. Just as with React, you can use the spread operator on an object of properties, and it will be merged with the other properties you’ve specified and applied to the element:

  render() {
    const props = {
      class: 'world',
      href: 'https://www.oreilly.com/'
    };

    return (
      <a class="hello" {...props}>O'Reilly Media</a>
    );
  }

This would generate a link to oreilly.com with a class attribute equal to hello world.

Summary

This chapter explained how you can use render functions as an alternative to template strings to build HTML. You do this by using the createElement function, which takes three arguments: the tag name, the data object, and the children of the element. You can also use JSX instead of calling createElement, using babel-plugin-transform-vue-jsx.

Get Vue.js: Up and Running now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.