Vue has been around for a while now. It has been gaining immense popularity within the developer community and has managed to grab the attention of many react/angular developers. Much of this was owed to the absolutely simple and intuitive component APIs that exist in Vue. With Vue 3, however, the makers have decided to change(or introduce an alternative) the way we make components in Vue. This is the composition API.
When initially released, it received mixed reactions from the community but slowly and surely, most Vue developers have come to realise how easy it is to use and how clean it would make our application code. Let’s dig into it now and see how it compares with the standard options API from previous Vue versions.
Disclaimer: This tutorial assumes prior knowledge of javascript and VueJS. If you’re not familiar with them, I would highly recommend learning them - They’re awesome!
You can clone the initial code repo from here. I’ve simply created a normal signup page with a username
and a password
field and a submit button that could potentially make an API call to create the user. It also provides the users with the option to change the language between English and Spanish(Español). All of these features are implemented using the options API.
For demo purposes, the API used in this case is the JSONPlaceholder API that mocks post requests.
Everything of concern happens in the App.vue
file itself. Let’s take a look at it:
<template>
<div id="app">
<div class="form">
<label for="username">{{dictionary.usernameLabel}}:</label>
<input id="username" class="input" :value="username" @input="updateUsername" />
<label for="password">{{dictionary.passwordLabel}}:</label>
<input id="password" type="password" class="input"
:value="password" @input="updatePassword" />
<button class="submit" @click="submit">{{dictionary.signupLabel}}</button>
</div>
<div class="language-container">
<label for="language">{{dictionary.languageLabel}}: </label>
<input type="radio" name="language" value="en" v-model="language"/>English
<input type="radio" name="language" value="es" v-model="language"/>Español
</div>
</div>
</template>
<script>
import axios from 'axios';
import en from './constants/en.json';
import es from './constants/es.json';
export default {
name: 'app',
data() {
return {
username: '',
password: '',
language: 'en',
};
},
methods: {
updateUsername(event) {
this.username = event.target.value;
},
updatePassword(event) {
this.password = event.target.value;
},
async submit() {
// probably add some other business logic here :)
await axios.post('https://jsonplaceholder.typicode.com/users', {
username: this.username,
password: this.password,
});
alert('User created');
},
},
computed: {
dictionary() {
return this.language === 'es' ? es : en;
},
},
};
</script>
<style>
// some custom styles here
</style>
App.vue
We’ve created two fields in the data
that correspond to the two input fields in the app; username
and password
. We also have update methods for each of these data
fields; namely, updateUsername
and updatePassword
.
Note: I am aware that we could have used
v-modal
here but the idea of this article is to show how verbose code can become when using the options API
We also have a computed
property, dictionary
, that updated the language strings based on the selected language
.
Once the form is filled, the user can click the “Sign up” button that calls the submit
method in the component’s options. This function would typically contain some business logic corresponding to the application but at the moment, let us just make a call to the JSON placeholder API and display an alert is everything goes well.
So, at first glance, this code looks pretty good. It is something we are all familiar/comfortable with and has never really failed us.
However, if you look at the application from a “features” perspective, you will notice that we have a signup
feature. Despite being part of a single feature, most of the elements, like the data
fields, the update and submit methods
are part of different options. As the application grows and we have a couple more features in our App
component, this could get a little messy because all the code corresponding to a given feature is scattered between the different component options.
The creators of Vue have recognised this issue faced in some fairly large Vue projects and have proposed to create the composition api
which solves this problem.
Note: If you’re familiar with React, the composition api, simply put, is the Vue version of React hooks
Let’s use this new API in our application:
In the new API, Vue allows us to create just one option
, namely a setup
method that “sets” up our component. They have also provided us with hooks into the native functionality like the data
, computed
, and the lifecycle method options from the old API.
To understand this better, let’s refactor our current app to use the composition
API.
We need to first install the composition api package and register it in our app:
yarn add @vue/composition-api
Register this plugin in your main.js :
import Vue from 'vue';
// Import the composition API plugin
import VueCompositionApi from '@vue/composition-api';
import App from './App.vue';
// Register the composition API
Vue.use(VueCompositionApi);
Vue.config.productionTip = false;
new Vue({
render: h => h(App),
}).$mount('#app');
main.js
Note: The composition API is going to be available by default in Vue 3. You will not have to register a plugin every time. But since Vue 3 is in alpha at . the time of writing this article and we’re using Vue 2, we need to register it.
We will begin our refactoring with the most trivial way of maintaining reactivity in Vue apps - The data
attribute.
In Vue, we have two choices of maintaining this; ref
and reactive
. Typically, you would use ref
s when you need to make a single primitive value to have reactivity. reactive
can be used when we have something like an entire object to maintain its reactivity. In our case, the inputs we use with be ref
s.
Once refactored, the App.vue file should look like this:
<template>
<div id="app">
<div class="form">
<label for="username">{{dictionary.usernameLabel}}:</label>
<input id="username" class="input" :value="username" @input="updateUsername" />
<label for="password">{{dictionary.passwordLabel}}:</label>
<input id="password" type="password" class="input"
:value="password" @input="updatePassword" />
<button class="submit" @click="submit">{{dictionary.signupLabel}}</button>
</div>
<div class="language-container">
<label for="language">{{dictionary.languageLabel}}: </label>
<input type="radio" name="language" value="en" v-model="language"/>English
<input type="radio" name="language" value="es" v-model="language"/>Español
</div>
</div>
</template>
<script>
import axios from 'axios';
import { ref, computed } from '@vue/composition-api';
import en from './constants/en.json';
import es from './constants/es.json';
export default {
name: 'app',
setup() {
const username = ref('');
const password = ref('');
const language = ref('en');
const updateUsername = (event) => {
username.value = event.target.value;
};
const updatePassword = (event) => {
password.value = event.target.value;
};
const submit = async () => {
// probably add some other business logic here :)
await axios.post('https://jsonplaceholder.typicode.com/users', {
username,
password,
});
alert('User created');
};
const dictionary = computed(() => (language.value === 'es' ? es : en));
return {
username,
password,
language,
updateUsername,
updatePassword,
submit,
dictionary,
};
},
};
</script>
<style>
// some custom styles here
</style>
App-composition.vue
As you can see, all our code now resides in just one setup
function. We have used ref
for maintaining the reactivity of the username
, password
and the language
keys. Our methods
have now just become normal functions and we have used the computed
hook to evaluate the value of our dictionary
based on language
's value.
Few things to note here:
1. We have gotten rid of all the “mysteries” from the old options API. No this
, you’re free to use arrow functions now. This is purely just functional programming now.
2. You can access only the things you “return” from your setup
method in your template. This makes things more readable.
3. When you use the value of ref
s in the template, simply using the name suffices; but in the setup
method, we need to use ref.value
. I believe this is something that the core team is working on.
4. All the “parts” of a feature that were earlier scattered across the different “options” can now be colocated. Great right?!
For the people who are still unimpressed, there’s one more step we can go from here. The composition API allows us to “compose” different features and then use them in our component.
To understand this better, let’s move our login and language features into two different composition functions.
We could create a useLogin
composition function for the login flow and a useLanguage
for the language feature:
Note: Here, we are creating the two functions in the same file but ideally, you would create them in separate files dedicated to each composition function; That was you will be able to share the compositions between different components
<template>
<div id="app">
<div class="form">
<label for="username">{{dictionary.usernameLabel}}:</label>
<input id="username" class="input" :value="username" @input="updateUsername" />
<label for="password">{{dictionary.passwordLabel}}:</label>
<input id="password" type="password" class="input"
:value="password" @input="updatePassword" />
<button class="submit" @click="submit">{{dictionary.signupLabel}}</button>
</div>
<div class="language-container">
<label for="language">{{dictionary.languageLabel}}: </label>
<input type="radio" name="language" value="en" v-model="language"/>English
<input type="radio" name="language" value="es" v-model="language"/>Español
</div>
</div>
</template>
<script>
import axios from 'axios';
import { ref, computed } from '@vue/composition-api';
import en from './constants/en.json';
import es from './constants/es.json';
const useLogin = () => {
const username = ref('');
const password = ref('');
const updateUsername = (event) => {
username.value = event.target.value;
};
const updatePassword = (event) => {
password.value = event.target.value;
};
const submit = async () => {
// probably add some other business logic here :)
await axios.post('https://jsonplaceholder.typicode.com/users', {
username,
password,
});
alert('User created');
};
return {
username,
password,
updateUsername,
updatePassword,
submit,
};
};
const useLanguage = () => {
const language = ref('en');
const dictionary = computed(() => (language.value === 'es' ? es : en));
return {
language,
dictionary,
};
};
export default {
name: 'app',
setup() {
return {
...useLogin(),
...useLanguage(),
};
},
};
</script>
<style>
// some custom styles here
</style>
App-composition-modular.vue
As you can see, the setup
function now is very small and readable. This way, you can extract tiny features as composition functions and compose them together in different combinations in different components!!
Well, we’ve seen how the composition API works and seen a few ways it can be implemented. It’s about the right time to discuss the value that this has brought:
useLanguage
function can be used in multiple components that require such translation.this
and that 😂I hope that this article served as a good primer to the new composition API and got you excited about Vue 3.
Hit me up on twitter or leave any suggestions/views in the comments below 👇
Cheers ☕️
☞ JavaScript Programming Tutorial Full Course for Beginners
☞ Learn JavaScript - Become a Zero to Hero
☞ E-Commerce JavaScript Tutorial - Shopping Cart from Scratch