With the new version, Vue has become more powerful, with things like performance improvements, reduced bundle size, better typescript support, Composition API, and much more.
Here we are going to explore the last one, the Composition API. I have played with it to build some of my latest projects and I would like to share from that experience 4 reasons why you should consider having this in your toolbox.
What is the Composition API?
The composition API is an alternative to the traditional way of writing Vue components. instead of having to export a Vue object with different properties.
export default {
name: 'MyComponent',
components: { ... },
props: { ... },
data() { return { ... } },
computed: {},
mounted() { ... }
methods: {}
}
Now Vue has decoupled its API in standalone functions that allows us to manage reactivity, control lifecycle. It would look like:
Notice: at the time of writing this post Vue 3.2.6 is the latest version and
<script setup>
is no longer an experimental feature and in fact, is the recommended way to go, so in the code examples written here, we are going to use this style.
<script setup>
import { ref, reactive, computed } from 'vue';
// Every defined variable is exposed to the template
const salute = ref('Hello world');
const persona = reactive({
name: 'Jesus Guerrero'
age: 25,
});
const greeting = computed(() => {
return `${salute.value}, I am ${persona.name} and have ${persona.age} years old`.
});
// Every function is available in template
const sumAge = () => {
persona.age ++;
}
</script>
Let me explain a little bit what we are doing here. First, we are importing ref and reactive which are the principal ways of adding reactivity to our variables. ref is for primitive types like Number
, String
. but in order to make them reactive, Vue has to wrap it in an Object
that's why we have to use salute.value
to access its real value.
Reactive on the other hand is for non-primitive values like Objects
and Arrays
and we don't need to use .value
to manage them and the result is deep reactive.
The other function we imported from Vue was computed that does the exact same thing as Vue 2 but now is a function that accepts a callback. Its value is going to change whenever one of its reactive dependencies changes.
That looks pretty different from what we had before. doesn't it?
I want to stop at this point to explain something that you might be wondering, why this was needed and what is the real value it brings to the Vue ecosystem.
Why the Composition API was needed
There has been a lot of claims about the complexity added with Composition API but it's nothing to fear. First, because the options API is right there, is valid and is going to coexist with the composition API (but is better if you do the change), and second, once you get used to it and understand its use cases you'll never look back.
I got two main reasons:
Vue worked very well for middle and large projects even though was a claim that other frameworks were better to handle the complexity of large projects but let's be honest, large projects are complex themselves. with that said Vue could do a better job covering some edge cases like:
- Introduce better support to Typescript.
- Provide a better way to share functionality across different components and across the app (a more explicit way than Mixins at least).
Vue core team shared the reasons of the changes in a section you can read if you have not read yet. They put a magnific work and in my personal case, I think that they brought solutions to all that and went beyond.
Benefits of the Composition API
I'd like to share some benefits you're given by having Composition API in your toolbox:
1- Order code by Domain.
With the Options API where we ordered our code not by logical concerns and behavior
export default {
name: 'MyComponent',
components: { ... },
props: { ... },
data() { return { ... } },
computed: {},
mounted() { ... }
methods: {}
}
Let's add another responsibility to our previous example, and add a list of Interest that the end-user can filter.
<script setup>
import { ref, reactive, computed } from 'vue';
// presentation
const salute = ref('Hello world');
const persona = reactive({
name: 'Jesus Guerrero'
age: 25,
});
const greeting = computed(() => {
return `${salute.value}, I am ${persona.name} and have ${persona.age} years old`.
});
// interests
const interest = reactive({
searchText: "",
list: ['javascript', 'hashnode', 'vue', 'vue3', 'laravel', 'supabase', 'productivity'],
});
const executeSearch = (searchText, list) => {
let filteredList = list
if (searchText) {
filteredList = list.filter((item) =>
item.includes(searchText.toLowerCase())
);
}
return filteredList;
};
const filteredInterest = computed(() => {
return executeSearch(interest.searchText, interest.list);
});
</script>
We used a computed, another reactive object but is grouped by domain, by logical concern. It's clear for us to understand that the first part of the code is related to the presentation and the second one to the interests list.
2 - Elegant support to typescript.
It's impossible to deny the huge benefits typescript brings to the JS community, managing large codebases with different people is better with types, static checks and help offered by the code editors makes everybody's lives easier.
The typescript support in Vue 3 is neat, simple and elegant:
<script lang="ts" setup >
import { ref, reactive, computed } from 'vue';
...
// interests
const interest = reactive({
searchText: "",
list: ['javascript', 'hashnode', 'vue', 'vue3', 'laravel', 'supabase', 'productivity'],
});
const executeSearch = (searchText: string, list: string[]): string[] => {
let filteredList = list
if (searchText) {
filteredList = list.filter((item) =>
item.includes(searchText.toLowerCase())
);
}
return filteredList;
};
const filteredInterest = computed(() => {
return executeSearch(interest.searchText, interest.list);
});
</script>
3 - Composables.
As we now have the reactivity exposed we can extract functionality to its own space and reuse them where we want.
Let's extract the search functionality into its own composable and let's use typescript to add types.
// useSearch.ts
import { computed, Ref } from "vue"
export const useSearch = (searchText: Ref<string>, list: Ref<string[]>) => {
const executeSearch = (searchText: string, list: string[]): string[] => {
let filteredList: string[] = list
if (searchText) {
filteredList = list.filter((item) =>
item.includes(searchText.toLowerCase())
);
}
return filteredList;
};
const filteredList = computed(() => {
return executeSearch(searchText.value, list.value);
});
return {
filteredList
}
}
Notice how we declare a function that takes two refs searchText
and list
we need to pass ref here and not plain string
and array
because we need reactivity
to return a filtered list that will change whenever we search or add another item to the list.
To use this in our components just import and call the composable like this:
<script setup lang="ts">
import { reactive, toRefs } from "vue";
import { useSearch } from "../../utils/useSearch";
const interest = reactive({
searchText: "",
list: ['javascript', 'hashnode', 'vue', 'vue3', 'laravel', 'supabase', 'productivity'],
});
const { searchText, list } = toRefs(interest);
const { filteredList: filteredInterest } = useSearch(searchText, list);
</script>
Notice how we use our composable like a normal function, and we imported a new function from Vue here toRefs
we cant destructure a reactive
object like a normal javascript object without losing reactivity instead we need to transform from reactive to ref
as our composable accepts a ref
s as arguments and that's exactly what toRefs does.
The code here is extremely readable and explicit. We just reduced our component size in lines, extracted a functionality that can be used in other components with different UI's and items but the same needs, and we are going to understand faster what's happening if we let the codebase for three or six months and need to change something (hopefully).
This is maybe the more powerful use case that the Composition API solves important libraries offer composable functions like vueuse, useMotion and others that have made it easier to work with APIS like local storage, animation, and sensors.
4 - Exposed magic.
The last one is personal, I like the fact that it feels natural to share reactive functionality in vue in a more explicit way than mixins; I don't need to read to know where a function comes from if I am using two mixins in a component
Wrapping up
I am not a pythonist but I think that its zen manifest does a great job describing the philosophy of coding, the lines I like the most are:
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
The composition API is a good example of this philosophy, is Vue exposing its powers and giving them to us to build more powerful and scalable apps, improve the developer experience and make the code more portable across the app, and why not, between projects.
That's all for today, In the next post we'll focus a little more on refs
and reactive
as we understand better what is and the benefits of the new API.
Thanks for reading, as always the comments and my Twitter are open to any question about the topic and have a nice day.