How to start with Vue 3 composition API and why?

Vue 3 allows us to choose a better way of handling common logic inside Vue components by introducing composition API. As the name suggests, the main goal of this approach is to composite or stack standard methods and variables together, which makes code cleaner, easier to read and maintain. In this article you will learn:

  • How to use Vue 3 composition API
  • What are the differences between Vue 2 options API and Vue 3 composition API
  • How to handle basic component configuration
  • How to manage methods, computed, watch and local state in Vue 3

Benefits of composition API

Vue 3 comes with more advanced features available to developers. With Vue 3 you can stack common logic in blocks and keep everything more manageable than in Vue 2. No more blurred responsibilities inside components. Why should you switch to Vue 3? Because it uses a modern way of handling problems, and a new way of handling reactivity which is not blocked by browsers limitations. With Proxy for reactive objects and getter/setter for refs you won't face any hiccups from uncontrolled loss of reactivity or unexpected side effects. And that’s just a start! 

There is a plugin that provides a way to use composition API in Vue 2 projects with a “special” setup hook, but now, after Vue 3 stable release, there are few differences which makes Vue 3 even more reliable. So let’s dig into Vue 3 vs Vue 2 differences.

Environment setup and base components

First of all, you have to create a new Vue.js project. For that we are going to use Vite as a build tool instead of webpack – not only because it is now fully supported by Vue 3, but also because it is a lot faster than webpack. 

How to install Vue.js 3? The whole setup is available at Vue 3 docs, so I will skip it. It is quite easy and fast.

Let’s create our first component on which I am going to show you how to manage Vue 3 components, what are the differences between options API (Vue 2) and composition API, and how to handle Vue 3 basics.

Let’s clean up the base project, delete HelloWorld component, create a new one (for example – BaseComponent) and import it inside App.vue:

<template>
 <div>Hello there!</div>
</template>
<script setup>
</script>

Basic component options, script setup, and how to write code in Vue 3

As you can see, the first difference is that we are not going to use a setup hook inside the script, instead you can use sugar syntax by implementing setup directly as script attribute. That allows you to write JS code directly inside the script tag.

Let’s start by adding some basic functionality to your component:

<template>
 <div>Hello there! {{ firstName }}</div>
 <div>
 <input type="text" v-model="firstName">
 </div>
</template>
 
<script setup>
import { ref } from 'vue'
const firstName = ref('')
</script>

Now, let’s look at two major changes. 

Firstly, you don’t need a single root element inside your Vue template! Yes, now you can have two, three, or even more separated HTML elements or components inside the template block. Isn’t that great? 

Secondly, you have created a new variable firstName, which is a ref. Refs are reactive variables which you can use directly inside the Vue template. Vue 3 reactivity is observing those variables and reacting for their every change. You also don’t need to return anything from the script tag, due to the setup attribute.

The v-model on input is working the same. So after writing down your name in it you should notice it next to Hello there!.

In Vue 2 you would add firstName as a data() property element. Everything added to data() in Vue 2 is transferred to Vue 3 ref and reactive (what’s the difference between ref and reactive you will find in our article about Vue 3 reactivity in-depth – coming soon!)

Let’s do something more complex. What if you want to use Vue 3 computed, to combine a few variables and watch for changes?

<template>
 <div>Hello there! {{ combinedName }}</div>
 <div>
   <input type="text" v-model="firstName" />
   <input type="text" v-model="surname" />
 </div>
</template>
 
<script setup>
import { ref, computed } from 'vue';
const firstName = ref('');
const surname = ref('');
 
const combinedName = computed(() => `${firstName.value} ${surname.value}`);
</script>

computed is also importable from the vue package, and you need to use it like a normal callback function. As you can see, firstName and surname inside the script tag have key reference .value. That’s because Vue 3 ref is directly exposed to the template. If you want to change its value inside the script tag or read it, you need to use the .value key.

👉 If you are changing the ref value, always change the .value itself, otherwise you will lose reactivity!

firstName.value = "John" // GOOD!
firstName = "John" // BAD!

If you want to access computed inside the script tag, you also have to use the .value key.

There’s a major change to how watch is working, because now, watch is more controllable. Also, there’s a new method watchEffect:

<template>
 <div>
   {{ count }}
   <button @click="count++">Increment</button>
 </div>
</template>
 
<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
 
watch(count, (prevValue) => {
 console.log('hello from watch!')
 if (prevValue === 10) {
   console.log('we have 10!')
 }
})
 
watchEffect(() => {
 console.log('hello from watchEffect!')
 if (count.value === 10) {
   console.log('we have 10!')
 }
})
</script>

In this example, you will use watch and watchEffect to become familiar with how both of those methods are working. In your console, you will firstly see the console.log coming from watchEffect, because watchEffect is firing like watch with immediate: true option, immediately at first render. After clicking the button, both watch and watchEffect will fire. Why? Because watch like in Vue 2, is watching directly the changes of count ref, but watchEffect is aware of every change on every variable that was used inside of it, so you don’t need to specify any values.

One of the most important things about watch and watchEffect is that both of those methods can be stopped! For example, if your condition has been fulfilled, you can turn off watching for your declared variable:

<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
 
const watchObserver = watch(count, (prevValue) => {
 console.log('hello from watch!')
 if (prevValue === 10) {
   console.log('we have 10!')
   watchObserver()
 }
})
 
const watchEffectObserver = watchEffect(() => {
 console.log('hello from watchEffect!')
 if (count.value === 10) {
   console.log('we have 10!')
   watchEffectObserver()
 }
})
</script>

So, what’s really the difference? Basically, Vue.js documentation says it all, but when it comes to real life projects, I would say that you should rather go with watch for most of the time than with watchEffect, because you have more control over what is happening and when. But if you have multiple dependencies, and you don’t want to create arguments chaining in watch like this…

<script setup>
import { ref, reactive, watch, watchEffect } from 'vue'
const count = ref(0)
const name = ref('')
const surname = ref('')
const state = reactive({ a: 10 })
 
watch(
 [count, name, surname, () => state.a],
 (
   [countValue, nameValue, surnameValue, stateValue],
   [prevCountValue, prevNameValue, prevSurnameValue, prevStateValue]
 ) => {
   // there's a lot going on....
 }
)
</script>

…of course it’s quite a corner case, but anyway, in situations like that – go with watchEffect.

What about methods? Methods are now normal functions. No need to import anything, just write it down inside the script tag:

<template>
 <div>
   {{ generateRandomNumber() }}
 </div>
</template>
 
<script setup>
const generateRandomNumber = () => Math.floor(Math.random(10) * 10);
</script>

That’s it about basics. With that knowledge, you are ready to start creating your own components and logic! Next time you will try your hand at advanced component options, so stay tuned.

Like what you’ve read? Feel free to share this article on Twitter, LinkedIn or Facebook by using the super-easy share buttons on your right!

Great ideas require great technology solutions