Vasily Kandinsky (1866–1944): Composition 8 (Komposition 8)

React developers will love the Vue Composition API

Steven Straatemans
Frontmen
Published in
12 min readNov 12, 2019

--

Ok, let me be clear first. I’m not really big on Vuejs. I like React. And not just the way of writing Class components. No I like the functional components. Just functions, love it!

A colleague asked me, last October, to do a meetup talk with him about Vue and especially about it's Composition-API. It was a new API, still in the RFC status. But you could use it and it looked really promising.
I nodded and smiled, when all the while, my head was desperately thinking about a way out of it. Vue? I don’t know anything about Vue. What was he thinking? Sure, I did a small project with it once. But to do a talk about it? And to say I fell in love with Vue at that time? Not really, no. I really liked the reactivity of Vue, but I found it weird to create these component objects and have these options where I can have my functionality.

While my head was filling with excuses to get out of it, my colleague talked more about this API. At least I think he did. His mouth was moving, I wasn’t really paying attention. I started paying attention when he mentioned that the old RFC for this API was called Vue-hooks. Why didn't he say that in the first place?

Because last year, when I was at React Conf 2018 they announced React Hooks. I.was.excited! No more writing classes, no more componentDidMount, componentWillUnmount or componentWillReceiveProps. Just functions. I started using hooks in my projects and never looked back.

Anyway, I started investigating this Vue API and turns out it works really nice. And on top of that, in some aspects I like it better than the React hooks (but don't tell the React community).

But Vue is fine as it is?
Ok Vue works really nice. It's fast. It has that reactivity going for it. But it has it's problems as well. The same problems that React had to deal with by the way.
1. When components become big, it becomes harder to read the code.
2. There is no nice way to extract the code from the component and make it re-usable.

The first problem is this; the standard way that components are written in Vue. It's an object with some options where you put your functionality, like methods, data, computed, etc. By the way, from now on, I will call this the Option-based API, because…uhh…, well like so many others do.
In this API the code is written per option not by functionality. Basically what this means is this. When you have a component, which has some filter functionality in it, it will have some functionality in the methods, in the data, maybe in computed.

# App.vueexport default {
name: "app",
data: () => ({
...
visibility: "all"
...
}),
computed: {
filteredTodos: function() {
return filters[this.visibility](this.todos);
},
...
...
},
methods: {
...
...
filterAll: function() {
this.visibility = "all";
},
...
}
};

In a small component, this is fine, but when the component gets larger, it will be hard to find the forest through the trees. Especially for other developers in your team, who need to change something in your component.
The new Composition API will get rid of the options and have all the functionality within one setup method. Here you can order all the functionality next to each other for easy readability.

The second problem is the issue of reusability. Because all the code is divided in the different options, it's harder to extract the code from the component and make it reusable.
With the Composition-API you can extract all the methods, data, watchers, etc for one specific functionality in to a composition function and save it somewhere in a different file. Ready to be used by whatever component you want. And then call on that composition function in your setup method of your component.

But what about Mixins?
True, mixins, or even Higher-order components, make it possible to extract the code from the component and gives you the ability to reuse the code in other components. There are a couple of downsides though.
For example when you import a mixin in your component it's not clear what the mixin exposes to the component. It just says the name of the mixin.

# App.vueexport default {
mixins: [filterMixin],
...
};

You would have no idea what this filterMixin does for your component, other than what the name of the mixin tells you.
This will become an even bigger problem when you have multiple mixins. It makes it really hard to debug a certain property, if you don't know where it's coming from.
It is also possible that 2 different mixins expose a method with the same name and different functionality.

The Composition API doesn't have these problems, because the properties need to be explicitly exposed from the composition function before they can be used. And when 2 of the methods turn out to have the same name, they can be renamed to what ever you want. So no name clashing.

So I have to learn all these new things?
Don't worry. You don't have to learn anything new, if you don't want to. If you want, everything will stay the same in Vue. It's just an extra tool under your belt. You can even use the Composition API and the Option-based API in the same component. The most important thing is:
This is not new functionality. The Composition API just exposes Vue Core functionality. But then as standalone functions. We will see that later when we work with the API.

Sounds nice. Can I use it?
The API will be standard available in the new Vue 3. One small problem though. There is no official release date yet for version 3. At the moment of writing it is still in the Pre-Alpha stage. And on the official roadmap the official release is scheduled for the first quarter of 2020(https://github.com/vuejs/vue/projects/6).

But the good news is that the Composition API is compatible with Vue 2 as well and can be installed as a plugin.

I want to see it in action!
Sure, lets dive into some code. For the example I customized this Fiddle in the Vue Docs (https://jsfiddle.net/yyx990803/4dr2fLb7/?utm_source=website&utm_medium=embed&utm_campaign=4dr2fLb7). I removed some code to make the project a bit simpeler. And moved some functions to a mixin.
What is left is a simple to do list with some nice styling. It fits our needs perfectly. It has something of everything. It has some data, computed values, watchers, methods and a mixin. We are ready to start.

Lets start at the very beginning. A very good place to start.
First we need to install the node package for the composition-API plugin.

npm install @vue/composition-api — save

after that we just need to add the plugin to our main.js

# main.jsimport VueCompositionApi from ‘@vue/composition-api’; Vue.use(VueCompositionApi);

That's all there is to it. Now we are ready to use hooks! uhh…the Composition-API.
First thing we need to do is add the setup option to our App.vue

# App.vueexport default {
name: 'app',
setup() {
},
...

So you add another option?
Uhm…yes. I know this was about removing options from the object. But things need to get worse before they get better.
In this method all the rest of the action will happen. There are some things you need to know about the setup method.
The method is called right after the component instance is created and before the beforeCreate() lifecycle (https://vuejs.org/v2/guide/instance.html).

Update: In the first version of this article I stated that the setup method runs after the beforeCreate lifecycle. This is not the case. It runs before the beforeCreate and after the component instance is created. Thanks to Prashant Palikhe for pointing this out.

It resolves before other 2.x options like data, computed and methods. So it will have no access to properties defined there.
But, because it resolves first, everything the setup method returns is exposed to the this context and therefor they can be used by the 2.x options.
This means that the Composition API and the normal Options-based API can be used together in the same component. Which is really useful when refactoring as we will see later.

The setup method has 2 optional properties:

setup(props, context);

Props are just the properties that are given to the component.
Context exposes properties previously accessed using this (attrs, slots, parent, root, emit).

In the setup method we also find the biggest difference with the React hooks. And what I think consider to be an improvement on the Composition API from the hooks from React.
And that is in the way that React and Vue work on the inside.
React has certain rules that you need to follow when implementing hooks. If you don't follow these rules strange things can happen during rendering. So much so that they have created an Eslint plugin for this, that they highly recommend you use. Don’t call Hooks inside loops, conditions, or nested functions (https://reactjs.org/docs/hooks-rules.html).
These rules exist because the effects in a React component run every time the component is rendered and re-rendered. If they are in a conditional statement they sometimes exist and sometimes don't. Which can give really strange side-effects.
Because the setup method only runs once, Vue does not have this problem. So if a hook is in a conditional it will be called or it won't. The effect stays the same in the entire lifespan of the component instance.

Ok enough about setup. Now what?
Now we can start to move functionality from the options to the setup method.
Let's start with the data. There are 2 ways to create state in the Composition API. Let's import them and take a look.

import { ref, reactive } from ‘@vue/composition-api’;

There is the ref and the reactive method.
What ref does, is taking the given value and turn in into a reactive object. When you would console.log it you would see a getter and setter and a value.
This means you can't do this:

const counter = ref(0);
counter++; // NaN

This will result in NaN, because counter is an object. The correct way would be this:

const counter = ref(0);
counter.value++; // 1

When adding the property to the template you don't have to use the .value. The template will recognise that this is a reactive object and will add the .value itself.

Don’t forget to return the object at the end of the setup method. Only the things returned at the end of setup can be used by the template.

Then there is the second method, reactive.
This also creates a reactive object, but the effect goes deep, so every attribute will be reactive as well. The upside of using reactive is that you don't have to use the .value to get the value of an attribute. This is done by the object itself.
Beware, you can't destructure the object to seperate properties. Because the object takes care of the reactivity, when you destructure all you are left with are the normal values. And used in the template nothing will update.
Luckily there is a helper function that will help you to get around this problem. This helper will create ref objects from every property and you can do this. It's called toRefs and you just need to import it.

return {…obj}; // will loose the reactivityimport { toRefs } from ‘@vue/composition-api’;
return {...toRefs(obj)}; // will destructure to seperate ref objects

Our refactored setup will now look something like this:

# App.vue...
setup() {
const newTodo = ref('');
const visibility = ref('all');
const state = reactive({ todos: todoStorage.fetch() });
return {
newTodo,
visibility,
todos: state.todos,
};
},
...

So when do I use ref and when reactive?
That is a good question and even the Vue team doesn't have a clear answer when to use what. I would say use ref for primitives and reactive for objects, just like you would normally use them. It feels like you are doing the same as normal.

Next up, methods
It doesn't matter if the methods are in the methods-option or in a mixin. We can move them to the setup. There is nothing to import from the Composition-API. It's just functions, but we do need to do some refactoring.

# App.vuemethods: {
addTodo: function() {
var value = this.newTodo && this.newTodo.trim();
if (!value) {
return;
}
this.todos.push({
id: todoStorage.uid++,
title: value,
completed: false,
});
this.newTodo = '';
},
}
...

And when we move it to the setup method it will look like this:

setup() {
const addTodo = () => {
let value = newTodo && newTodo.value.trim();
if (!value) {
return;
}
state.todos.push({
id: todoStorage.uid++,
title: value,
completed: false,
});
newTodo.value = '';
}
}

First thing you'll notice is that we removed the this context. It's not needed in the setup method. And because setup resolves before other Option-based methods, everything that is returned from the setup method is accessible from this.
So we removed this. We also need to remember that the newTodo is now a reactive object, so when we want the value from that object we need to do that by calling the .value property.
Todos is now also in a reactive object and is part of the state. So we call state.todos.
Nothing more to do then to return the method from the setup. And do all the other methods as well. This also means also the methods that are in the mixin. Now we can remove the methods and the mixin option from the component. Bye bye.

Seems easy. Now do the computed values
We have 2 computed values in our component:

# App.vue...
computed: {
filteredTodos: function() {
return filters[this.visibility](this.todos);
},
remaining: function() {
return filters.active(this.todos).length;
},
},
...

There is nothing more to do than to import the computed method and add them to the setup method. Don't forget to return them, so we can use the values in the rest of the component.

# App.vueimport { ref, reactive, computed } from '@vue/composition-api';
...
setup() {
const filteredTodos = computed(() => {
return filters[visibility.value](state.todos);
});
const remaining = computed(() => {
return filters.active(state.todos).length;
});
...
},
...

Let's do that small watcher also. Import the watch 'hook':

# App.vueimport { ref, reactive, computed, watch } from '@vue/composition-api';
...
setup(){
watch(() => todoStorage.save(state.todos));
...
},
...

When the setup runs, the watcher functions will be called once, so that Vue can register the reactive objects that are used in the watcher. Whenever one of those objects change, the registered watchers also run.

After this, you can remove the watch and computed options from the component and don't forget to return the computed values. You don't need to return the watcher. Once that is registered it will do things by itself.

And now we refactored all the Option-based API stuff into the new Composition-API.

# App.vueexport default {
name: 'app',
setup() {
...
return {
...
};
},
};

It's…so beautiful.

It's a mess!
So you moved all the functionality from 4 options to 1. Good job. The setup method is one big pile of junk. It's a mess!
True. We are not completely finished yet. Because although we have dumped everything in one big method, you can see that all things can be arranged by functionality. We can have all the things doing with Todo next to each other and all the filter stuff as well.

Let's take the filter functionality as an example.

# App.vue...
const visibility = ref('all');
const filterAll = () => {
visibility.value = 'all';
};
const filterActive = () => {
visibility.value = 'active';
};
const filterCompleted = () => {
visibility.value = 'completed';
};
...

The great thing of the Composition API is that we can now extract all this functionality to a composition function or 'custom hook' :

# /hooks/useFilter.jsimport { ref } from '@vue/composition-api';export default () => {
const visibility = ref('all');
const filterAll = () => {
visibility.value = 'all';
};
const filterActive = () => {
visibility.value = 'active';
};
const filterCompleted = () => {
visibility.value = 'completed';
};
return {
visibility,
filterAll,
filterActive,
filterCompleted,
};
};

And import it into the component:

# App.vue
import useFilter from './hooks/useFilter';
setup(){
const {
visibility,
filterAll,
filterActive,
filterCompleted,
} = useFilter();
... return {
visibility,
filterAll,
filterActive,
filterCompleted,
}
}

You can also do this for the todo functionality. And after you do this, the component will look something like this.

Besides the upside of having the code ordered by functionality, you now also have the ability of code reuse. Look at the useFilter hook for example. It should be useful in other parts of your application which needs filter functionality. This was much harder in the normal Option-based API.

But doesn't this do the same as a mixin?
Well yes, it makes it easier to reason what is going on in the hook. You only imported the mixin, without showing what methods or data it was exposing. Which made it a lot harder to debug, for instance.
The hook returns the functionality and is therefore very clear in the component itself.

So that's it?
Well almost. For example every lifecycle method in the standard Option-based API now has it's counter part in the Composition API. For example mounted is now called OnMounted and updated will be OnUpdated. But basically they work the same as the watcher. You just call them in the setup method.

Conclusion
I love this new API. I always found the Vue Option-based API a bit weird. Why have all your functionality in an object. I needed to remember all these options I could use. Now I only have to remember the setup option and I’m ready to go.
And because of the Reactivity of Vue it’s really simpler to reason about the whole thing than the React hooks.

I guess if you have been a Vue developer for a while, then the new API needs a little getting used to. But if you give it a chance, I bet you will see the upsides.

Thanks for reading. If you liked it, don't forget to 👏. I would really appreciate it.

--

--

Steven Straatemans
Frontmen

I'm a frontend developer @ Frontmen. A dutch frontend company. History buff, podcast addict and dad.