Table of Contents
URL: https://www.progressiverobot.com/vuejs-introduction-render-functions/
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.
[label 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">
{{pea.name}}
</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.