The next version of Vue is around the corner and we can already try some new features, like Vue Composition API, which is heavily inspired by React Hooks. A lot of developers are excited about it, others are not so sure. Let’s see how to use it and what the big deal is.
Just recently the Vue core team released pre-alpha version of the next Vue version – Vue 3. It is faster than the current Vue 2 and will also offer new and exciting features. One of those features is Vue Composition API.
The Composition API is heavily inspired by React Hooks. As a developer who works with both React and Vue on a daily basis, I could not be happier about this feature. It will allow us to create reusable and stateful business logic and make it easier to organize related code. What is more, it is free of caveats that exist in React Hooks. For instance, in React, Hooks cannot be called conditionally and are called on every render. You can read more about the differences here. Anyway, what is the big deal with this Composition API?
Vue is very easy to use and has a great API that is beginner-friendly and simple to understand. However, when components get bigger and bigger, it is much harder to maintain and understand them as different pieces of business logic are mixed up together. At the moment, there are a few ways of handling this, e.g., mixins, higher order components (HOCs), and scoped slots, but each of them has their own disadvantages.
For instance, HOCs is a pattern derived from React in which one component is wrapped with another component that spreads reusable methods/state values into the former. However, this pattern does not really play well with Single File Components, and I have not seen many developers adopt HOCs in Vue.
Mixins on the other hand are quite simple as they will merge object properties like data, methods, computed, etc., into a component via mixins
property. Unfortunately, when there are more and more mixins, there is a higher chance for naming collisions. In addition, it is not so obvious where certain methods and state are coming from. It might require scanning through all the mixins to find out a particular method definition.
I am a fan of neither mixins nor HOCs, and when needed I would always choose scoped slots. However, scoped slots are also not a silver bullet, as you might end up with a lot of them at some point and basically more and more components are created just for the sake of providing a way for creation of reusable stateful logic.
Next, let’s have a look at the Composition API and how it works. For that we will create a new project and try it out!
Scaffold a new project with Vue-Cli. You can follow installation instructions from the documentation. We will use a package called @vue/composition-api
as it will allow us to try the new API. After setting up the project and installing the required library open ‘main.js’ file and add these lines so we can use new features.
import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);
Let’s start with a simple example. Create a new component called Count.vue
. It will only have a button, counter, computed property, and a method to increment the count. Simple, but it shows how the crucial pieces of Vue components, namely ‘data’ state, computed properties, and methods, can be created.
<template>
<div class="count">
<button @click="increment">Count is: {{state.count}}, double is: {{state.double}}</button>
</div>
</template>
<script>
import { reactive, computed } from "@vue/composition-api";
export default {
name: "Count",
setup() {
const state = reactive({
count: 0,
double: computed(() => state.count * 2)
});
const increment = () => state.count++;
return {
state,
increment
};
}
};
</script>
We have a new property called setup
. This is where we can use functions for creating state, computed properties, etc. The setup
method should return an object which will include anything that should be available in our component.
We imported two functions – reactive and computed. You can probably guess what these mean. Reactive
is actually an equivalent of Vue.observable
, which is available in Vue 2, while computed
doesn’t really need any additional explanation. It is just a different way of directly creating a computed value.
Now you can import the Count.vue
component in the App.vue
and try it out in your browser.
<template>
<div id="app">
<Count />
</div>
</template>
<script>
import Count from "@/components/Count";
export default {
name: "app",
components: { Count }
};
</script>
This is a simple example, but let’s try to do something fancier that you could potentially use in a project. Let’s create a function called useApi
that will have a state for data
, api_status
, and initFetch
function. It will accept a url
and options
object. We will use it to fetch a random dog from dog.ceo
API. Create a new component called Dog.vue
and between <script>
tags add this code:
import { reactive, computed, toRefs } from "@vue/composition-api";
const useApi = (url, options = {}) => {
const state = reactive({
data: null,
api_status: ""
});
const initFetch = async () => {
try {
state.api_status = "FETCHING";
const response = await fetch(url);
const data = await response.json();
state.data = data.message;
state.api_status = "FETCHING_SUCCESS";
} catch (error) {
state.api_status = "FETCHING_ERROR";
}
};
if (options.hasOwnProperty("fetchImmediately") && options.fetchImmediately) {
initFetch();
}
return {
...toRefs(state),
initFetch
};
};
This time, besides reactive
and computed
we are also importing toRefs
. I will explain why we need it in a moment. In the useApi functions we declared state
constant which has reactive data
and api_status
. Further, we have the initFetch
function that will update api_status
as well as fetch data for a url that was provided as an argument.
Next, we check if the options
object has the fetchImmediately
property. It will be used to indicate if an API call should be initialized when a component is created. Finally, we return an object with spread state values and the initFetch
function. As you can see, we do not spread the state directly, but instead we spread a result of toRefs
functions. The reason behind it is that when values returned from the state would be destructured, they would not be reactive anymore. Therefore, toRefs
wraps each value in a ref
thanks to which state values will cause a Vue component to re-render as it should when state values are changed.
The useApi
function is now ready to be used, so let’s set up the rest of the component.
export default {
setup() {
const { data, api_status, initFetch } = useApi(
"https://dog.ceo/api/breeds/image/random",
{
fetchImmediately: true
}
);
return {
dogImage: data,
api_status,
fetchDog: initFetch
};
}
};
As I mentioned before, we can destructure properties that we need from the useApi
without losing reactivity. In addition, the object that is returned from the setup has renamed properties to better indicate what they are for. Now, the last thing to add is the template.
<template>
<div style="margin-top: 20px;">
<div v-if="api_status === 'FETCHING'">Fetching</div>
<div v-else-if="api_status === 'FETCHING_ERROR'">Error</div>
<div v-else-if="api_status === 'FETCHING_SUCCESS'">
<img :src="dogImage" style="display: block; max-width: 500px; height: auto; margin: 0 auto;" />
</div>
<div v-else>Oops, no dog found</div>
<button style="margin-top: 20px;" @click.prevent="fetchDog">Fetch dog</button>
</div>
</template>
The template consists of a few divs
which are rendered conditionally depending on the api_status
. Due to passing fetchImmediately: true
to the useApi
, a random dog will be fetched at the start, and you can fetch a different one by clicking the Fetch dog
button. It will initialize the fetchDog
function which basically is the initFetch
function returned from the useApi
function. The last step is to import the Dog.vue
component in App.vue
and render it.
That is all we need. You now have a reusable function for fetching data that can be reused across your components. If you wish you can get more creative and improve it further. If you would like to know more about the Vue Composition API, then definitely have a look at the documentation. You can find the code in my GitHub repository.
Thomas Findlay is a 5-star rated mentor, full-stack developer, consultant, technical writer and the author of “React - The Road To Enterprise” and “Vue - The Road To Enterprise.” He works with many different technologies such as JavaScript, Vue, React, React Native, Node.js, Python, PHP and more. Thomas has worked with developers and teams from beginner to advanced and helped them build and scale their applications and products. Check out his Codementor page, and you can also find him on Twitter.