Image

advanced-components

5 min read
Last update: December 19, 2021

Functional Components

In the previous lesson we learned how to create render functions. With this new super power there are two patterns you can use. The first, which we’ll dive into this lesson, is a functional component which allows you to:

  • Create fast-loading presentational components
  • Create wrapper components

You could think of it like a Component on a diet. Perhaps we want a custom header just for presentational logic and we decide to write it using a render function:

<div id="app">
    <big-topic>
        Hiking Boots
    </big-topic>
</div>

<script src="vue.js"></script>
<script>
    Vue.component('big-topic', {
        render(h) {
            return h('h1', 'Topic: ' + this.$slots.default)
        }
    })

    new Vue({
        el: '#app'
    })
</script>

Notice how we’re accessing the default slot, using this.$slots.default.

Using a normal Component for this presentational Component is a little overkill. This is where functional components start to shine:

A Functional Component:

  • Can’t have its own data, computed properties, watchers, lifecycle events, or methods.
  • Can’t have a template, unless that template is precompiled from a single-file component. That’s why we used a render function above.
  • Can be passed things, like props, attributes, events, and slots.
  • Returns a VNode or an array of VNodes from a render function. Unlike a normal component that has to have a single root VNode, it can return an array of VNodes.

As you might imagine (without less functionality) a functional component is a little faster. We can make a functional component by simply adding the option:

Vue.component('big-topic', {
functional: true, // <----- render(h, context) { // Notice the new context parameter return h('h1', context.slots().default) } }) ``` Notice how we now use the `context` parameter to access slots? This `context` argument is how we get access to to things like props, children, data, parent, listeners, and slots inside a functional component since we no longer can use `this`. The official Vue documentation [goes into more detail](https://vuejs.org/v2/guide/render-function.html#Functional-Components). If we’re using Vue’s single file components we can declare a functional components at the template level. Here is the above example as a single file component: ```html <template functional>
    <h1>
        <slot></slot>
    </h1>
    </template>
    ```

    Yes, that could be the ONLY content in your `.vue` file. No export default, no props, no data, no methods, and it will just render out the template. This is great for **presentational templates**.

    ## Functional Wrapper Components

    Functional components are great to use when you need a way of programmatically delegating to a specific component. The Vue.js documentation has a great example I’d like to walk you through, simplified first.

    Let’s say you want to create a `SmartTable` component. If the list passed in is Empty render using one component, but if it’s not Empty use another component.

    ![](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371456068_1.jpg?alt=media&token=97482fdd-22ec-4c1b-8e52-d6e28cd7dadf)

    The `SmartTable` component only needs to know how to delegate, it doesn’t have to be very smart, so it’s going to be a functional component.

    ```html
    <div id="app">
        <smart-table :items='vehicles'>
    </div>

    <script src="vue.js"></script>
    <script>
        const EmptyTable = {
            template: `<h1>Nothing Here</h1>`
        }
        const NormalTable = { // Normally this would be more complex
            template: `<h1>Normal Table</h1>`
        }

        Vue.component('smart-table', {
            functional: true,
            props: {
                items: {
                    type: Array
                }
            },
            render(h, context) {
                if (context.props.items.length > 0) { // Delegate 
                    return h(NormalTable, context.data, context.children)
                } else {
                    return h(EmptyTable, context.data, context.children)
                }
            }
        })
        new Vue({
            el: '#app',
            data: {
                vehicles: ['Fiat', 'Toyota', 'BMW']
            }
        })
    </script>
    ```

    As you might expect, when the vehicles Array is empty we see the EmptyTable, and when it has one or more item it displays the Normal Table:

    ![](https://firebasestorage.googleapis.com/v0/b/vue-mastery.appspot.com/o/flamelink%2Fmedia%2F1578371308347_2.png?alt=media&token=28d7a0f7-4a6a-4f04-84cf-a60947ff4188)

    With the `h(NormalTable, context.data, context.children)` line we’re rendering the component and passing through all our data which includes attributes, event listeners, and props so Normal table will have access to them.

    ## Aside: Destructuring Context

    When you have a function which receives a JavaScript object as a parameter you can use ES6 destructuring to split it out into variables. It’s not uncommon to see people do this with the context object like so:

    ```javascript
    render(h, { props, data, children }) {
    if (props.items.length > 0 ) {
    return h(NormalTable, data, children)
    } else {
    return h(EmptyTable, data, children)
    }
    }
    ```

    As you can see, the code gets a little simpler.

    ## The More Complex Example

    The example in [the Vue documentation](https://vuejs.org/v2/guide/render-function.html#Functional-Components) is a more complex version of what we have above. Instead of having just two different components to render our list, we have four in our smart-table. Take a read:

    ```javascript
    // Here are the four components we're delegating to

    var EmptyList = { /* ... */ }
    var TableList = { /* ... */ }
    var OrderedList = { /* ... */ }
    var UnorderedList = { /* ... */ }

    Vue.component('smart-list', {
    functional: true,
    props: {
    items: {
    type: Array,
    required: true
    },
    isOrdered: Boolean
    },
    render: function (createElement, context) {

    // This function returns which component to use to render
    function appropriateListComponent () {
    var items = context.props.items

    if (items.length === 0) return EmptyList
    if (typeof items[0] === 'object') return TableList
    if (context.props.isOrdered) return OrderedList

    return UnorderedList
    }

    // Now call the appropriate component.
    // Remember that h is short-hand for createElement
    return createElement(
    appropriateListComponent(),
    context.data,
    context.children
    )
    },

    })
    ```

    As you can see, our functional component just wraps around our other four components.

    When you need some sort of wrapper or delegating component, and you care about speed, a functional component might be the best way to go. In our next lesson, we’ll dive back into the Vue.js source to figure out more internals of the rendering and mounting process.