Vue2: Component composition where a single slot is a root node
One of the powerful patterns used across many frontend frameworks is component composition. In React, it is a foundation and the default approach to structuring component hierarchy. In Vue 2 it looks like that's not necessarily true.
I wanted to create a component which accepts content using composition pattern. One of the requirements was that this component had to have a specific html structure, there couldn't be any tags between parent and child because existing styling wouldn't work. It means that this structure wouldn't work:
<template>
<div>
</slot>
</div>
</template>
However, if you remove the root wrapping <div> tag you will encounter this error:
Cannot use <slot> as component root element because it may contain multiple nodes.
The way to implement this component and satisfy the requirements was to use render function. This way we create nodes fully programmatically, completely omit the <template></template> and its limitation to have a single root node.
<script lang="ts">
export default {
render: function (createElement) {
if (!this.$slots.default) {
throw Error("Default slot is mandatory.");
}
return this.$slots.default[0];
},
};
</script>
This will work if we have a single slot - but what if we want to have more? If we try to return an array of nodes from the render function, we will experience similar error as in the beginning:
Multiple root nodes returned from render function. Render function should return a single root node.
There is only one option where it is possible to return more than one node from the render function. That's functional component.
Functional component can be used when the component "doesn’t manage any state, watch any state passed to it, and it has no lifecycle methods. Really, it’s only a function with some props.". As we can see it fixes one issue but introduces its own limitations.
<script lang="ts">
export default {
functional: true,
render: function (createElement, context) {
return context.children;
},
};
</script>