In this article, we’ll look at how to define and use Vue.js render functions.
Basics
We can define and use render functions to programmatically display elements in our Vue app.
An example that can benefit from using render functions are components where there’re lots of v-if
cases.
For example, if we want to display headings of various sizes with one component, we can define the following component:
src/index.js
:
Vue.component("variable-heading", {
template: "#variable-heading-template",
props: {
size: {
type: Number,
required: true
}
}
});
new Vue({
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<script type="text/x-template" id="variable-heading-template">
<h1 v-if="size === 1">
<slot></slot>
</h1>
<h2 v-else-if="size === 2">
<slot></slot>
</h2>
<h3 v-else-if="size === 3">
<slot></slot>
</h3>
<h4 v-else-if="size === 4">
<slot></slot>
</h4>
<h5 v-else-if="size === 5">
<slot></slot>
</h5>
<h6 v-else-if="size === 6">
<slot></slot>
</h6>
</script>
<div id="app">
<variable-heading :size="1">foo</variable-heading>
</div>
<script src="src/index.js"></script>
</body>
</html>
The code above works by passing in the size
value to the variable-heading
component as a prop and then the v-if
in component template will determine which element to display depending on the size
prop’s value.
It’s very long and has duplicate slot
elements.
We can use render functions to reduce the code above to:
src/index.js
:
Vue.component("variable-heading", {
props: {
size: {
type: Number,
required: true
}
},
render(createElement) {
return createElement(`h${this.size}`, this.$slots.default);
}
});
new Vue({
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<variable-heading :size="1">foo</variable-heading>
</div>
<script src="src/index.js"></script>
</body>
</html>
The code above works by us adding a render
method to our variable-heading
component, which returns a Virtual DOM node, or VNode for short, by calling createElement
function with the tag name as the first argument, and the children HTML nodes as an array as the second argument.
A Virtual DOM Node tells Vue what to render on the screen, which will be turned into a real DOM node.
The this.$slot.default
have the child HTML nodes passed in between the opening and closing tags of variable-heading
.
Nodes, Trees, and the Virtual DOM
HTML code are rendering dynamically by rendering a dynamic tree of elements called the DOM tree. DOM standards for the Document Object Model, which abstracts the HTML structure into a tree.
A tree has nodes and some nodes like text and HTML nodes are displayed in our browsers’ screens.
Vue manipulates and renders the DOM tree for us so that we don’t have to do it ourselves.
For example, we can create a component with a render function as follows:
Vue.component("variable-heading", {
data() {
return { msg: "hi" };
},
render(createElement) {
return createElement("p", this.msg);
}
});
CreateElement Arguments
The createElement
takes a few arguments.
The first argument is the HTML tag name, component options or async function that resolves to one of the 2 kinds of items mentioned before. It’s required.
The second argument is an object that has data corresponding to the attributes that we want to add to the template. It’s an optional argument.
The third and last argument is a string or array of the child VNodes built using createElement
. This is also optional.
The Data Object
The data object has many properties that can control the attributes, classes, event handlers, inline styles of the created VNode.
The following properties are available:
class
It’s an object which has the class names that we want to include in the class
attribute of the HTML element we render.
For example, we can write the following:
class: {
foo: true,
bar: false
}
Then the foo
class is included in the class
attribute and bar
isn’t.
style
An object with the styles that we want to include. For example, we can write the following:
style: {
color: 'yello',
fontSize: '12px'
},
Then the styles above will be included with the element that’s rendered.
attrs
attrs
object has the attributes and values that we want to include in the rendred HTML element. For example, we can write:
attrs: {
id: 'foo'
}
Then our rendered HTML element will have the ID foo
.
props
The props
object has the component’s props with the corresponding value. For instance, we can write:
props: {
foo: 'bar'
}
Then we can pass in the foo
prop with the value 'bar'
.
domProps
domProps
object have the DOM properties that we want to pass in. For example, we can write:
domProps: {
innerHTML: 'foo'
}
Then our rendered HTML element has the text foo
inside.
on
With the on
object, we can set event handlers for our rendered HTML element. It’s used for listening to events that are emitted via $emit
.
For example, we can write:
on: {
click: this.clickHandler
}
to listen to Vue click events.
nativeOn
We can listen to native browser events with the nativeOn
object. It haves the same object as on
expect that we have native event handlers instead of Vue event handlers.
directives
We can pass in the directives
array to pass in one or more directives with their values, arguments, modifiers, etc.
For example, we can write:
directives: [
{
name: 'custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
Then we can apply the custom-directive
directive with modifiers, arguments, values, and expressions into the VNode.
scopedSlots and slot
We can pass in slots into our directive by setting it to a function that returns an element.
For scoped slots, we can write:
scopedSlots: {
default: props => createElement('span', props.text)
}
For slots, we can just set the value as the name of the slot.
Other special top-level properties
key
takes the key, ref
takes a string ref name, refInFor
is a boolean that is true
if the same ref
name is applied to multiple element.
Full Example
We can set the properties above as in the example below:
src/index.js
:
Vue.component("anchor-text-block", {
render(createElement) {
const [slot] = this.$slots.default;
const headingId = slot.text.toLowerCase().replace(/ /g, "-");return createElement("div", [
createElement(
"a",
{
attrs: {
name: headingId,
href: `#${headingId}`
}
},
this.$slots.default
),
createElement(
"p",
{
style: {
color: "green",
fontSize: this.fontSize
}
},
this.paragraphText
)
]);
},
props: {
fontSize: {
type: Number,
required: true
},
paragraphText: {
type: String,
required: true
}
}
});new Vue({
el: "#app"
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<anchor-text-block paragraph-text="foo" :font-size="12"
>link</anchor-text-block
>
</div>
<script src="src/index.js"></script>
</body>
</html>
In the code above, we created an anchor-text-block
component that 2 props, paragraph-text
and font-size
as indicated in the props
property that we have in the component’s code.
The kebab case in the template is automatically mapped to camelCase in the JavaScript code.
In the render
method, we called createElement
, which creates a div
, and then in the second argument, we have an array with the a
and p
elements created. This is where we passed in both props.
We also set the attrs
property to #link
since we have link
as the slot’s inner text since that’s what we added between the opening and closing tags.
In the p
element, we set the fontSize
style from the prop and set the color to green.