How to Create Reusable Components with the Vue 3 Composition API

In this tutorial, we’ll explore how to use the Vue 3 Composition API and its latest code reusability capabilities.

Code sharing and reusability are one of the cornerstones of software development. Since the earliest days of programming, the problem of code repetition has made programmers invent strategies for keeping their code DRY, reusable and portable. As time has passed, these strategies have been constantly polished and improved, and new ones are constantly developed.

This applies equally to Vue as to other programming languages and frameworks. As the Vue framework has evolved, it it has continued to offer much better reusability approaches.

What Is Composition API and Why It Was Created

Let’s consider what makes a piece of code reusable. For me, there are three main principles of reusability:

  • Code abstraction. A piece of code is abstract when it can suit multiple different use cases (like classes in many programming languages).
  • Code portability. A piece of code is portable when it can be used not only in different places in one project but also in different projects.
  • Code decoupling (or loose coupling). A piece of code is decoupled from another one when changing one doesn’t require changing the other. They’re as independent of each other as possible. Of course, complete decoupling is impossible — which is why the more accurate term used by developers is “loosely coupled”.

The Composition API is a new strategy for building and structuring Vue 3 components. It incorporates all of the three principles described above and allows for creating abstract, portable, and loosely coupled components that can be reused and shared between different projects.

Motivation to Add the Vue Composition API to the Framework

The motivation for the Composition API to be added to Vue 3 is clear and simple: producing more compact and defragmented code. Let’s explore this a bit more.

When I found Vue for the first time, I was hooked by its Options (object-based) API. It seemed to me way more clear and elegant in contrast to the Angular and React equivalents. Everything has its own place and I can just put it in there. When I have some data, I put it in a data option; when I have some functions, I put them in a methods option, and so on:

// Options API example export default { props: ['title', 'message'], data() { return { width: 30, height: 40 } }, computed: { rectArea() { return this.width * this.height }, }, methods: { displayMessage () { console.log(`${this.title}: ${this.message}`) } } }

All this seems quite ordered, clean, and easy to read and understand. It turns out, however, that this is valid only while an app is relatively small and simple. As the app and its components grow more and more, the code fragmentation and disorder increase.

When the Options API is used in large projects, the code base soon starts to become like a fragmented hard disk. Different parts of the code in a component, which logically belong together, are spread in different places. This makes the code hard to read, understand and maintain.

This is where the Composition API comes into play. It offers a way to structure the code in order, where all logical parts are grouped together as a unit. To some extent, you can imagine the Composition API as a disk defragmentation tool. It helps you to keep the code compact and clean.

Here’s a simplified visual example:

Options vs the Composition API

As you can see, a component’s code built with Options API could be quite fragmented, while a component’s code built with the Composition API is grouped by features and looks much easier to read and maintain.

Vue Composition API Advantages

Here’s a summary of the main advantages the Composition API offers:

  • Better code composition.
  • Logically related blocks are kept together.
  • Better overall performance compared to Vue 2.
  • Cleaner code. The code is logically better ordered, which makes it much more meaningful and easy to read and understand.
  • Easy to extract and import functionality.
  • TypeScript support, which improves IDE integrations and code assistance, and code debugging. (This is not a feature of the Composition API, but it’s worth mentioning it as a feature of Vue 3.)

Composition API Basics

Despite its power and flexibility the Composition API is quite simple. To use it in a component, we need to add a setup() function, which in fact is just another option added to the Options API:

export default { setup() { // Composition API } }

Inside the setup() function, we can create reactive variables, and functions to manipulate them. Then we can return those variables and/or functions, which we want to be available in the rest of the component. To make reactive variables, you’ll need to use the Reactivity API functions (ref(), reactive(), computed(), and so on). To learn more about their usage, you can explore this comprehensive tutorial about the Vue 3 Reacivity system.

The setup() function accepts two arguments: props and context.

Props are reactive and will be updated when new props are passed in:

export default { props: ["message"], setup(props) { console.log(props.message) } }

If you want to destructure your props, you can do this by using toRefs() inside the setup() function. If you use ES6 destructuring instead, it will remove props reactivity:

import { toRefs } from 'vue' export default { props: ["message"], setup(props) { // const { message } = props <-- ES6 destructuring. The 'message' is NOT reactive now. const { message } = toRefs(props) // Using 'toRefs()' keeps reactivity. console.log(message.value) } }

Context is a normal JavaScript object (not reactive) that exposes other useful values like attrs, slots, emit. These are equivalents to $attrs, $slots, and $emit from the Options API.

The setup() function is executed before the component instance creation. So you won’t have access to the following component options: data, computed, methods, and template refs.

In the setup() function, you can access a component’s lifecycle hook by using the on prefix. For example, mounted will become onMounted. The lifecycle functions accept a callback that will be executed when the hook is called by the component:

export default { props: ["message"], setup(props) { onMounted(() => { console.log(`Message: ${props.message}`) }) } }

Note: you don’t need to call the beforeCreate and created hooks explicitly, because the setup() function does a similar job by itself. In a setup() function, this isn’t a reference to the current active instance, because setup() is called before other component options are resolved.

Comparing the Options API with the Composition API

Let’s make a quick comparison between the Options and Composition APIs.

First, here’s a simple to-do app component, built with the Options API, with abilities to add and remove tasks:

<template> <div id="app"> <h4> {{ name }}'s To Do List </h4> <div> <input v-model="newItemText" v-on:keyup.enter="addNewTodo" /> <button v-on:click="addNewTodo">Add</button> <button v-on:click="removeTodo">Remove</button> <transition-group name="list" tag="ol"> <li v-for="task in tasks" v-bind:key="task" >{{ task }}</li> </transition-group> </div> </div> </template>
<script> export default { data() { return { name: "Ivaylo", tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"], newItemText: "" }}, methods: { addNewTodo() { if (this.newItemText != "") { this.tasks.unshift(this.newItemText); } this.newItemText = ""; }, removeTodo() { this.tasks.shift(); }, } }; </script> 

I’ve omitted the CSS code here for brevity and because it’s not relevant. You can see the full code in the Vue 2 Options API example.

As you can see, this is quite a simple example. We have three data variables and two methods. Let’s see how to rewrite them with the Composition API in mind:

<script> import { ref, readonly } from "vue" export default { setup () { const name = ref("Ivaylo") const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"]) const newItemText = ref("") const addNewTodo = () => { if (newItemText.value != "") { tasks.value.unshift(newItemText.value); } newItemText.value = ""; } const removeTodo = () => { tasks.value.shift(); } return { name: readonly(name), tasks: readonly(tasks), newItemText, addNewTodo, removeTodo } } }; </script> 

As you can see in this Vue 3 Composition API example, the functionality is the same but all data variables and methods are moved inside a setup() function.

To recreate the three data reactive variables, we use the ref() function. Then, we recreate the addNewTodo() and removeTodo() functions. Note that all uses of this are removed and instead variable names are used directly followed by the value property. So instead of this.newItemText we write newItemText.value, and so on. Finally, we return the variables and functions so they can be used in the component’s template. Note that, when we use them in the template, we don’t need to use the value property, because all returned values are automatically shallow unwrapped. So we don’t need to change anything in the template.

We make the name and tasks read-only to prevent them from any changes outside of the component. In this case, the tasks property can be changed only by addNewTodo() and removeTodo().

When the Composition API is a good fit for a component and when it isn’t

Just because some new technology is created doesn’t mean you need it or must use it. Before deciding whether to use a new technology, you should think about whether you really need it. Although the Composition API offers some great benefits, using it in small and simple projects can lead to unnecessary complexity. The principle is the same as with Vuex usage: it can be too complicated for small projects.

For example, if your components are mostly single-feature — that is, they do only one thing — you don’t need to add unnecessary cognitive load by using the Composition API. But if you notice that your components are getting complex and multi-featured — they handle more than one single task and/or their functionality is needed in many places in you app — then you should consider using the Composition API. In medium to large projects with lots of complex, multi-featured components, the Composition API will help you produce highly reusable and maintainable code without unnecessary hacks or workarounds.

So you can take the following rules as a general advice:

  • The Options API is best for building small, simple, single-feature components whose functionality requires low reusability.
  • The Composition API is best for building larger and more complex, multi-featured components whose functionality requires higher reusability.

Continue reading How to Create Reusable Components with the Vue 3 Composition API on SitePoint.

Similar Posts