Here’s the code for the App.vue file:

It’s pretty standard, including:

a template which uses each of the 4 components a script block which imports the components, and instantiates the App Vue instance with those components and a couple of methods a style block (I went overboard on the styles, see full source on Github.)

First up: a standard, single file component

The first version of the component is a standard single file component:

First version, a standard, single file component

We’re specifying the markup we want and using our passed-in props, which we’ve defined with some validation. The only notable items are using v-on:click="$emit('click’)" to emit a click event (because we want the triggered method to live on the App component, not on this child component), and using v-if="$slots.default" to detect if anything (in our case, a caption) was passed to fill the default slot (see vm.$slots documentation). If not, we do not show the figcaption markup that wraps the default slot.

Next: a standard component using a render function

The next approach is a standard component using a render function instead of a template.

While the concepts of functional components and render functions are really entirely separate, they are closely associated in the Vue.js documentation (explanation of functional components is nested within the ‘Render Functions & JSX’ page), and I’ve seen very few examples out there of functional components with a traditional template. So, we better figure out render functions too.

Here’s the code:

Second version, a standard component using a render function

The big change obviously is that the template is gone and instead we have a render property. Its value is the render(createElement){} function. createElement is the function we can use, inside the render function, to build up a tree of virtual DOM nodes (VNodes), which eventually gets added by Vue to the actual DOM.

The createElement function takes 3 arguments:

a string of the html element to be created a data object specifying all the various attributes of the VNode and an array of strings (for text nodes) or other VNodes that will be wrapped by the created element.

You can see in the render function above, it’s a pretty painful process to create even for a relatively small tree. In this example, we:

create the img element create the figcaption element, if there is content in the default slot create the tags markup, if tags are passed as a prop pass all of the above in an array as the third argument to our final call to createElement , which returns a VNode of the wrapping figure element.

Whew.

Because this is just a standard component, it has an instance and we can use this to access our props and $slots . To achieve the behavior of v-if from our template, we manually check for this.$slots.default and return an empty string if it’s not present. Where we used v-for in the template, here we use Array.map to return a separate span VNode for each tag. Exhausting, honestly.

You can reintroduce a template-like syntax using JSX, which seems a little bit crazy, but maybe it makes sense in some cases.

Number three: a functional component using a render function

The third version is our first functional component, as you can tell by the functional: true property:

Third version, a functional component using a render function

This component also uses a render function, which looks similar to the previous one, but with some important differences. Instead of using this , we use a second argument provided to the render function, context . Using object destructuring we are pulling out just the properties we need from the context object: props , listeners , and slots .

Slots is a function which returns an object with our various slots, so where we used $slots.default previously, we can use slots().default .

There are a couple of esoteric things to note about two properties of the context object. One, children and slots() give you similar, but slightly different things. Read about the difference here. Two, listeners is an alias for data.on .

Best for last: a functional, single-file component

The last version is again a single-file component, but this time using the functional keyword. This was the hardest one to dig up information on, but in the end . The functional keyword is mentioned in the docs, but only in passing, with not much detail (so out of character!). There is this tantalizing snippet in the vue-loader docs.

Compare this to the first, standard component, version:

Last version, a functional, single-file component

Here are the key diffs, side by side: