When To Use The New Vue Composition API (And When Not To)
Anthony Gore | February 17th, 2020 | 4 min read
The new Composition API caused a fair amount of controversy when it was first announced by the Vue team. But now that the dust has settled and we've had a chance to try the demo, most of us can see it has great potential.
But like every programming tool, we can expect it to have both benefits and drawbacks. So what I'm thinking about most now is not if to use it, but when to use it.
The RFC says that:
"the problems [the composition API] aims to address appear primarily in large scale applications".
But is it of any use in smaller applications? Or should it perhaps be avoided there?
In this article, I'll give you my thoughts and share a guiding principle that you may find useful.
Table of contents:
Motivating example
In the RFC, a motivating example is given of a large component with many features that became unwieldy under the options API.
The RFC then shows how refactoring this component to use the composition API allowed the functionality to be organized by feature instead of by option thereby restoring readability.
While this example is perfectly illustrative of when the composition API is needed, it left me somewhat confused because this example component is atypically large and complex.
Will the same benefits be accrued with the smaller, "regular" components that I most commonly deal with? To answer this, I think it's worth looking at the conditions under which a component gets so large and complex.
Component composition model
Along with reactivity, the component composition model is a defining characteristic of Vue.js.
While the docs don't explicitly say so, there's an implied aim to have a one-to-one relationship between a component and a "feature" in this model.
For example, a button-based component like the following will usually begin with one feature - to handle clicks it receives.
MyButton.vue
<template>
<button @click="doStuff">Click me</button>
</template>
But this component could be designed, or unintentionally grow, to include multiple features. For example, what if we decided to make it so this button spawned a drop-down?
At first, we might add the drop-down into the template and component state, but then our understanding of the component composition model would kick in again and we'd be compelled to abstract the new feature into a sub-component thus restoring the one-to-one ratio of components/features.
MyButton.vue
<template>
<button @click="doStuff">
Click me
<drop-down v-bind:some-prop="..." />
</button>
</template>
Multi-feature components
But there are times when more complex functionality doesn't fit neatly into the component composition model.
A good example of this is validated form inputs. These have multiple features that cannot be easily delegated into sub-components because the features are all complexly tied to the same piece of UI.
In this scenario, you may end up with two distinct features for the component - handling the input and validating the input. Such a component will be much better organized if broken down by feature using the composition API.
ValidatedFormInput.vue
useHandleInput() { ... }
useValidateInput() { ... }
export default {
setup () {
return {
...useHandleInput(),
...useValidateInput()
}
},
...
}
Logic extraction
Sometimes we want to create variations on a component. For example, we may want a button to have different colors when used in different forms on our site.
The component composition model provides a way of doing this - simply use template functionality driven by state passed as props. Other kinds of variations can be achieved with slots.
SimpleButtonCustomColor.vue
<template>
<button @click="doStuff" :classes="{ color }">
Click me
</button>
</template>
<script>
export default {
props: {
color: String
}
}
</script>
However, this pattern doesn't scale well. As variations become more numerous, or if the component is complex enough that variations are substantially different, they'll make the component progressively messier.
Making multiple variations of the components by abstracting the logic will be the best way forward. The composition API is superior here because it's far easier to abstract code that is already organized by feature.
For example, say you created a submit button component that you want to use across a variety of forms on your site. Template logic has hit its scaling limit, so the composition API would allow you to abstract the general logic into a composition function which, unlike a mixin, can very elegantly be consumed by multiple components:
useSubmitButton.js
export default {
handleSubmit() { ... },
...
}
SubmitButtonFullForm.vue
// General feature
import useSubmitButton from "./useSubmitButton.js";
// Specific feature
function useFullFormButton() { ... }
export default {
setup() {
return {
...useSubmitButton(),
...useFullFormButton()
}
}
}
SubmitButtonInlineForm.vue
// General feature
import useSubmitButton from "./useSubmitButton.js";
// Specific feature
function useInlineFormButton() { ... }
export default {
setup() {
return {
...useSubmitButton(),
...useInlineFormButton()
}
}
}
Regular components and the composition API
The motivating example from the RFC is atypical in that it's a complex multi-feature component. It's also possible that some of its logic gets reused in other components in the app as well. These attributes make it a perfect candidate for the composition API.
But when components are regular (single-featured and don't require logic abstraction for reuse), what can the composition API offer?
It seems we shouldn't apply the composition API blindly since the RFC states several drawbacks including the confusion between the refs
and reactive
functions, and the additional boilerplate of the setup
function.
In a regular component, the component composition model has not reached its limits, therefore the composition API is not solving any problem, just exposing the component to the stated drawbacks.
But don't use that as a hard rule since the composition API also has some secondary benefits:
- Better compatibility with TypeScript
- Better access to lower-level features like reactivity
- Allows easy integration of pre-packaged features e.g. useWeb
Why not both
In practice, any individual app will likely have some components that could benefit from the composition API, along with many that won't.
I looked through some of the Vue 2 apps I'd previously worked on (mostly small ones) and found that only 10-20% of the components were multi-featured or used mixins and would be stand to benefit from the composition API.
So this doesn't need to be a binary choice...why not just use both APIs together?
On one hand, it has to be optimal to use just one or the other in a project since switching between two APIs will increase cognitive load.
Plus, it may be that you get a smaller build if you only elect to use one of the two APIs as I believe the final release will allow you to tree shake away the API you don't need.
It's not a huge tradeoff, though, so I can see myself using both together when it's called for.
Wrapping up with a guiding principle
The composition API is still technically a proposed feature, so it may change, and even if it doesn't, I'm sure a lot of interesting possibilities will be conceived over time. I'm going to be keeping an open mind about it.
But I promised a guiding principle so here it is:
The composition API can be thought of in the same way as Vuex - it can be a useful tool, but it incurs a cost, so it shouldn't be used thoughtlessly. If you're not sure if you need it, you probably don't.
Even if you don't agree with that, I hope the above was tasty food for thought and I'd be interested to hear your opinion on when the composition API should be used.
About Anthony Gore
If you enjoyed this article, show your support by buying me a coffee. You might also enjoy taking one of my online courses!
Click to load comments...