twitter facebook github google-plus xing linkedin instagram
Technology

Introducing the Vue Composition API with a practical example

15 minutes reading time
Introducing the Vue Composition API with a practical example

Vue 3's main new feature is the Composition API, a new way to write and organize your components. Learn how to use it by refactoring a component using the classic Options API.

The Composition API is a great extension to Vue and the classic Options API that you are used to. It is especially useful for making code reusable and enhancing the readability of big components, but can be used for any component. Even if you haven't yet upgraded to Vue 3, you can use the plugin for Vue 2.

In this tutorial, we will extend the classic Vue Hello World-counter-example by displaying the current time to the user and updating it every second. We will start by using the Options API, to which we are accustomed, then refactor our code to use the Composition API, and finally extract a reusable composition function.

Creating a Vue 3 app

The simplest way to test out Vue 3 is by using the tool Vite. Run

yarn create vite-app composition-api-example
cd composition-api-example
yarn
yarn dev

and visit your local server. You should see the Hello World page with a counter which increases when clicked. The entrypoint for Vue is src/Vue.app, which renders the Vue logo and src/components/HelloWorld.vue. We will extend the HelloWorld.vue-component while keeping the code for the counter.

Building a clock with the Options API

Storing and displaying the current time

We start by implementing our clock with the classic Options API.

We want to display the current time to the user and will use a javascript Date-object for this. We extend the object returned from the data()-option with a property called currentTime:

<script>
  ...
  data() {
    return {
      count: 0,
      currentTime: new Date(),
    };
  },
  ...
</script>

To actually display the time, we add a paragraph at the bottom of our template, just above the closing </template>-tag:

<template>
  ...
  <p>{{ currentTime.toLocaleString() }}</p>
</template>

After saving the component, the page should reload automatically and now display the current time.

Updating the current time

The displayed time does not yet get updated - we would need to refresh the page for that. Instead, we want to automatically update the currentTime property from our data every second. To do so, we add the following code to our script-section, just beneath our data-option:

<script>
  ...
  methods: {
    updateCurrentTime() {
      this.currentTime = new Date();
    },
  },
  created() {
    this.intervalHandle = setInterval(this.updateCurrentTime, 1000);
  },
  beforeUnmount() {
    clearInterval(this.intervalHandle);
  },
  ...
</script>

Let's look at what we have done here:

  • The methods-option now contains a method updateCurrentTime, which just sets the currentTime-property from our data to the current time when called.
  • In the created-option, we set an interval which calls updateCurrentTime every second. We store the returned handle in this.intervalHandle so that we can access it later.
  • In the beforeUnmount-option (beforeDestroy in Vue 2) we clear the interval using the stored handle.

We can now watch our clock get updated, and are already done with the Options API-implementation!

Refactor to the Composition API

Setting up our auto-updating clock required quite some code, which is also placed all over the component - we needed to use the data, methods, created and beforeUnmount options. The data-option already contains code for two functionalities, the counter and the clock, and we can easily imagine that other options will also grow to contain code for different purposes. The Composition API offers a way to group our code by functionality instead of by option. To take advantage of this, we first need to move our code from the different options to the new setup-function, which we will do option by option:

data

We start by moving the data-option to the setup-function:

<script>
import { ref } from 'vue';
export default {
  ...
  setup() {
    const count = ref(0);
    const currentTime = ref(new Date());
    return { count, currentTime };
  },
  ...
}
</script>

Here we create a mutable property, called ref, for everything we previously returned from data. These references can be tracked by Vue's reactivity system, so that changes are automatically picked up. We make these references, count and currentTime, available to the component by returning them from the setup-function, meaning they can be accessed by using this.count and this.currentTime, just like when we returned them from data.

Inside the setup-function, we access and set the value of a reference by using .value, e.g. currentTime.value = new Date() or const newDate = currentTime.value. Outside of setup, e.g. in methods or the <template>-section, we need to omit the .value. We therefore do not need to change the implementation of updateCurrentTime, it will stay at this.currentTime = new Date().

methods

Next we will move the methods-option to the setup-function:

setup() {
  const count = ref(0);
  const currentTime = ref(new Date());
  const updateCurrentTime = () => { currentTime.value = new Date(); };
  return { count, currentTime, updateCurrentTime };
},

We just declare updateCurrentTime as an arrow function, and return it, so that the created-option still has access to this.updateCurrentTime. In the function, however, we just use currentTime instead of this.currentTime, and we need to assign the new date to currentTime.value.

created

Everything that happens in the created lifecycle hook can just be written inside our setup-function, as created is called just after setup:

  setup() {
    const count = ref(0);
    const currentTime = ref(new Date());
    const updateCurrentTime = () => { currentTime.value = new Date(); };
    const intervalHandle = setInterval(updateCurrentTime, 1000);
    return { count, currentTime, intervalHandle };
  },

We just set the interval in our setup-function, and return the intervalHandle as a plain integer, as it is still used by the beforeUnmount option. Note that there is no need to make a ref out of it, as it will never change and we therefore do not need reactivity. Additionally, we removed updateCurrentTime from the object returned from setup, as it is now only being used inside that function.

beforeUnmount

The last remaining option is the beforeUmount lifecycle-hook, which clears the interval. We replace it by using the onBeforeUnmount-function which we add to the import from 'vue', so that our whole script-section looks like this:

<script>
import { onBeforeUnmount, ref } from 'vue';

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup() {
    // Counter
    const count = ref(0);

    // Current time
    const currentTime = ref(new Date());
    const updateCurrentTime = () => { currentTime.value = new Date(); };
    const intervalHandle = setInterval(updateCurrentTime, 1000);
    onBeforeUnmount(() => { clearInterval(intervalHandle); });

    return { count, currentTime };
  },
}
</script>

We removed intervalHandle from the returned object, as we only need it within setup - just as we did with the updateCurrentTime function. This already shows a great benefit of the Composition API - we do not pollute our this. with internal variables which are not meant to be used in the template.

Making our code reusable

Now that all code lives in setup, we can strive to further organize it. In the last code snippet, we already divided our code into two logical groups - the counter and the current time sections.

Extracting composition functions

We can take these logical blocks and move them into so-called composition functions. We start by creating src/composables/useCurrentTime.js with the following contents:

import { onBeforeUnmount, ref } from "vue";

export const useCurrentTime = () => {
  const currentTime = ref(new Date());
  const updateCurrentTime = () => { currentTime.value = new Date(); };
  const intervalHandle = setInterval(updateCurrentTime, 1000);
  onBeforeUnmount(() => clearInterval(intervalHandle.value));
  return { currentTime };
};

We just copy and pasted the contents from the current time-section of the setup function, and added the neccessary imports. We can now refactor the script-section of our vue component to the following:

<script>
import { ref } from 'vue';
import { useCurrentTime } from '../composables/useCurrentTime';

export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  setup() {
    const count = ref(0);
    const { currentTime } = useCurrentTime();
    return { count, currentTime };
  },
}
</script>

That was easy! We could make the setup even shorter by returning { count, ...useCurrentTime() }, but the explicit style makes it easier to find where things are coming from.

Adding parameters

The interval duration of 1,000 ms is currently hardcoded. However, we can easily make a parameter out of it. This way, we can use our composition in different places, even if we sometimes want to update every second and sometimes only once per hour. Simply add a parameter to our useCurrentTime-function, and adjust the call in our component accordingly:

// HelloWorld.vue
const { currentTime } = useCurrentTime(1000);

// useCurrentTime.js
export const useCurrentTime = (refreshInterval) => {
  ...
  const intervalHandle = setInterval(updateCurrentTime, refreshInterval);
  ...
}

Further improvements

There are, of course, more things we could do to extend and improve our component:

  • Return currentTime from useCurrentTime as readonly.
  • Additionally return updateCurrentTime from useCurrentTime and enable the user to manually update the time in the HelloWorld-component by clicking a button.
  • Extract a composition function for the counter, which could include a increment-function which replaces the @click="count++" code which currently lives in the template.
  • Allow the refreshInterval-parameter of useCurrentTime to be modified at any time by making it a ref. Detecting changes for that parameter is possible with the watch function, and the update frequency can be changed by using clearInterval and setInterval.

Summary

We learned how to refactor existing Vue code from the Options- to the Composition-API, step by step. Doing so helped us move the code into one place instead of scattering it all over the component,and then grouping it into logical blocks. Finally, we were able to extract a composition function which contains a part of our component, and which can easily be used again in another component.

You are looking for the right partner for successful projects?
We'd love to help – let's get in touch!
Contact us