What does the Vue function API feel like
Recently a proposal for Vue.js 3.x has been published, and with it a whole new way to use Vue.js via function API that addresses many problems that were present in 2.x API.
I suggest you read the proposal and read the comments there as they explain the rational and the design choices that led to this API before reading my thoughts on this. I will discuss my thoughts without going into details.
What does it look like
In a nutshell, the basic example looks like this:
jsimport { value, computed, watch, onMounted } from 'vue';
const App = {
template: `
<div>
<span>count is {{ count }}</span>
<span>plusOne is {{ plusOne }}</span>
<button @click="increment">count++</button>
</div>
`,
setup() {
// reactive state
const count = value(0);
// computed state
const plusOne = computed(() => count.value + 1);
// method
const increment = () => {
count.value++;
};
// watch
watch(
() => count.value * 2,
(val) => {
console.log(`count * 2 is ${val}`);
}
);
// lifecycle
onMounted(() => {
console.log(`mounted`);
});
// expose bindings on render context
return {
count,
plusOne,
increment,
};
},
};
A few things jump to your mind when seeing this:
- Where ma
data
andcomputed
stuff at. - What the
frick
is a setup function.
The new API introduces functions that you will use to compose your functions instead of the component options we used to have in 2.x
:
value
function allows you to define reactive values (references), this is ourdata
replacement.computed
function allows you to define computed values based on said reactive references.watch
is self explanatory, it watches a reactive reference and executes a callback whenever its value change.onMounted
runs a callback whenever the component is being mounted, which ismounted
but without shackles.
Thatās right! data
and friends are kinda deprecated, which means we will have to use those functions to create our components with 3.0
onwards.
The setup
function is like an entry point for your component.
EDIT UPDATE 26/6/2019 š
data
and computed
and friends are no longer deprecated and the Vue 3.0 API will be compatible with Vue 2. The new API is additive. CALM DOWN š¤.
What are we trying to solve
This API is designed to solve the largest problem that plagues our web apps: re-using logic across components. We already had a few ways to solve this problem but those solutions fall short in a way or another:
-
mixins
: I really donāt encourage anyone to use mixins especially if you are going to register them globally withVue.mixin
. They are valid for multiple use-cases and if they solve your problem then donāt mind me. But there are some downsides to them, first it is not immediately clear which mixins are registered within a given app or a component, so we could have unused, conflicting units in our component, and aside from that it is not also clear which units are available on our component. I categorize this as amaintenance
problem, it may not hurt you but it will hurt whoever comes after you, could as well be you in a few weeks. -
HoC
: Higher order components is a design pattern which - akin to the higher order functions pattern - is defining functions that take a component and returns a new component with modified behavior or presentation. I rarely saw this pattern used in Vue.js community as it is more of a React thing but where ever this pattern might be, there is some cons that are inherit in the pattern itself: props clashing. Assuming you havename
prop in the wrapper component, it can easily conflict with the original component that wants that prop as well. Also the matter of performance is highly subjective but I wouldnāt worry about that in most cases. -
slot-scope
: Components that are renderless and usesv-slot
to provide data to their slot are probably one of the best APIs in Vue.js 2.x and I abuse it whenever I can, it allows you to declaratively share behavior, which is intuitive in my opinion and easily maintainable, but nesting multiple components can be gruesome and when abused you can end up with a pyramid of doom. This is as maintainable as the callback hell.
It becomes apparent that we are trying to re-use logic by using components as an interface for the shared logic, which introduces some complexities and challenges on their own, it would be nice we are able to share logic without worrying about how it will be used in components.
React Hooks to the rescue
Well, sort of. This new API is inspired by the newly hot addition to React API called hooks, and no its not the life-cycle hooks. This is what it looks like:
jsximport React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
While very similar, in my opinion it avoids some pitfalls and confusing stuff about the React Hooks API. But I always liked the āfeelā of React Hooks, it felt elegant as I was sharing behavior without having to introduce components in one way or another (HoC
and v-slot
) while also being very clear about which behavior is shared.
Itās all about pure logic
Putting aside how different things will be, the new Vue proposal allows us to define reactive data and build computed properties on top of them without having to assign them to components, it is context-free which allows us to better build and isolate behavior without thinking of extra logic like rendering.
Letās put words into examples, assuming we want to share some logic, say a GraphQL query fetching function API. We can start by doing something like this:
jsimport { value } from 'vue';
export const useQuery = (query) => {
// Define basic query state.
const data = value(null);
const errors = value(null);
const isFetching = value(false);
// This fn will run the query.
const execute = async () => {
isFetching.value = true;
const response = await apolloClient.query({
query,
});
data.value = response.data;
errors.value = response.errors;
isFetching.value = false;
};
// Return those "units" to be consumed by components.
return {
data,
errors,
isFetching,
execute,
};
};
In useQuery
we are only focusing on getting the GraphQL query executed while having some useful whistles around it, like being able to tell if the query is in progress or if it failed. This logic would work for virtually any query, and any component can consume this logic like this:
jsimport { useQuery } from './hooks/graphql';
import { onMounted } from 'vue';
const Todos = {
template: `
<div>
<ul v-if="!isFetching">
<li v-for="todo in data.todos">{{ todo.title }}</li>
<ul>
</div>
`,
setup() {
// BAM!
const { isFetching, execute, data } = useQuery(
gql`
{
todos {
id
title
}
}
`
);
// Let's run the query on mounted.
onMounted(() => {
execute();
});
// Make the data and the progress indicator available to the template.
return {
isFetching,
data,
};
},
};
What I love about this is we can explicitly specify what is available to the template, how many times did you need a private reactive value on your Vue instance without having to pollute the template with its reference? What about methods then? You had to define your internal methods on the methods
key whenever you wanted to do something with this
, this is no longer the case and we will be probably referencing this
a lot less frequently.
Taking it for a spin
When I finished reading the API, I felt like what we say around here āsitting on an eggā as in I was eagerly dying to tryout the new API and what Ideas might come when playing around with it. But unfortunately, Vue 3.x isnāt around until later this year.
So I decided what any reasonable Vue.js developer would do, implement the proposal using Vue.js 2.6+.
I hacked my way until I got a sufficient poc running on Vue.js 2.6+ API. Which is good for charting out your ideas and getting a āfeelā for the new API, but donāt dare use this in a real app, Iām merely playing around.
So lets see a couple of examples of making use of this API.
useMouse
So here is the useMouse
example shown in the RFC examples running:
GraphQL
this is the useQuery
example I shown earlier:
If you are interested in the source for the implementation, you can find it here.
EDIT UPDATE 22/6/2019
There is an active project actively implementing the RFC API, use that instead in your projects.
Conclusion
To be honest, I do have some concerns. I discussed this with my teammates and new developers and while some liked it, it does turn the entry-difficulty up a notch or two, Vue has built its popularity by being so easy to use, fast to adopt and quick pick up in production teams with small training costs.
But it will take some discipline to keep things maintainable.
I disagree, in my opinion The maintainability would improve, yes you probably have to do a better job keeping things neat and tidy, but you a much easier way to do that. I have come across many Vue.js projects and maintainability can be thrown out of the window even faster with component options, it took discipline to re-use components in 2.x, and if this API gets approved, we can at least think less about components, and more about re-usable logic and shift our discipline towards elegancy.