Introduction
Today we take a look back at last years’ Vue 3 release. On 18 September 2020, the third version of Vue was officially released, and the developers immediately loved it. This edition is completely new, written from scratch in TypeScript (TS). The question arises, then, what about the support for version two? In this article, we will answer this question, take a brief look at the biggest changes, compare them to the changes in React 17 and build understanding based on examples of how the new Composition API works.
What exactly does the Vue rewrite mean?
Following the blow of TypeScript's popularity, a new Vue release is written entirely in TS. This, in fact, benefits both Vue developers and its users, as TypeScript is statically typed, so it is harder to make a bug or typo, and improved syntax prompting will save you a lot of time. However, these are not the only benefits. The author of Vue Evan You said that a sizeable project like his is much easier to maintain if the type control mechanism helps us. In addition, the entry threshold is reduced and more people can contribute to the project and raise pull requests (PRs). In the context of maintainability, it is worth mentioning that Vue 3 has adopted the monorepo pattern, so many packages are divided to make everything even more modular.
Another great change coming with Vue 3 can be seen in the way it advises on reactivity, thanks to the the observation mechanism that is. As you probably know, in the previous version, a very popular concept of getters and setters was used for changes tracking by utilizing the Object.defineProperty(). The new version uses a more sophisticated search mechanism, namely the Proxy object together with Reflect. This is a very powerful mechanism of the ECMAScript which allows programmers to intercept and change fundamental language features which are called metaprogramming. If you’re not familiar with this concept in ECMA terms you can learn more about it here.
This is a good moment for mentioning Vue’s future support because, as you probably already know, Proxy object was introduced in ES6, which is not supported by IE and older browsers. So, Vue 2 is still available with LTS (Long-term Support) for 18 months, which means security updates. You can both migrate to Vue 3 or keep using the second version as it has some plugins enriching it to the v3 functionality.
Another super-fancy thing is optimization, especially the virtual DOM (VDOM). Firstly, the Vue 3 apps have been reported to be 50% smaller than their previous edition! Moreover, the performance speed has increased enormously - the relevant benchmarks show that both RAM usage and JS CPU time is greatly reduced in comparison with Vue 2! This was made possible by rewriting and optimizing the compiler. We can differentiate three main techniques used there. Firstly, the amount of required VDOM tree traversals has been significantly reduced. The second one is hoisting static variables and even templates outside the render function, which results in the reduction of the occupied memory. The less memory is occupied, the less potential changes to observe - so again, we’re gaining speed. The third optimization technique used predicts what properties each component will need to update. The compiler points out this information and runtime will use these hints to take the fastest path.
Getting closer to React?
Besides the general acceleration and changes under the hood, Vue 3 offers some interesting solutions - among them are Teleports and Fragments. If you’re familiar with React you’ll quickly notice some similarity and you wouldn’t be wrong. Teleport can be seen both as React’s Portal equivalent or expansion for a well-known library portal-vue. The purpose of this functionality is to hoist some DOM elements, like for example, modal to another place in the DOM tree. I must admit that Vue implementation is more friendly than the one known from React, where you have to write some logic to just create a portal. In Vue, a single attribute with a prop is enough.
<teleport to='#portal-target'>
<div>
I will be children of #portal-target element no matter where i’m declared!
</div
</teleport>
Another similar novelty that is even called identically as is in ReactJS is a Fragment - an element, which makes it possible to wrap multiple DOM elements without a need for wrapping it into a real DOM element . In the SPA (Single Page Application), we often encounter a situation where we want to return multiple nodes from one component. In Vue 2 you have to wrap your nodes into a single parent <div> to get rid of warnings. Even in React, you have to signalize explicitly that fact by wrapping multiple nodes into a single <React.Fragment>. In Vue 3, you don’t have to do anything because support for this framework comes out of the box! So, this code snippet is completely valid in Vue 3:
<template>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</template>
All these changes are even more interesting if we look at them in the context of React’s 17th version, which is called a “stepping stone” edition, as it brings no new features or breakthrough changes. Primarily, the focus of this release is to “make it easier to upgrade React itself.” It is easy to verify that this is the case by looking at the list of changes, where we will find only minor amendments and only one change worth mentioning, namely the event delegation. Prior to the React 17 version, the majority of events were delegated and attached by adding listeners to documents. React 17 brings with it a serious change because from now on event listeners are attached directly to the requested DOM element. Comparing changes to the React context, please notice the naming convention used by authors - we will describe it later.
What is a mixin?
To fully understand the last and most important change in the new Vue release, namely the Composition API, we have to step back a little and start from Mixins. In general, a mixin is a concept which is derived from the Object-oriented programming (OOP) paradigm. It is a class or interface that contains reusable code which is included or injected rather than inherited. An example of utilizing a mixin pattern in JS is obviously an Object.assign() which is used to copy properties from one object prototype to another. Let us take a look at an example of how Vue handles mixins.
export const paginationMixin = {
data() {
return {
page: 1,
maxPages: 10,
};
},
methods: {
handlePageChange(fetchFunction) {
const {
page,
maxPages,
} = this;
return (type) => {
if (type === 'next' && page < maxPages) {
this.page += 1;
fetchFunction();
} else if (type === 'prev' && page > 1) {
this.page -= 1;
fetchFunction();
}
};
},
},
};
This is a very basic example of a Vue-based mixin. After injecting it into a component, it will extend its internal state (using the “data()” function), and it will inject defined methods - in this case the “handlePageChange()” method. Every component to which a given mixin has been injected is able to use the provided functionality.
Why are mixins harmful?
It is not hard to tell that the approach mentioned above has certain shortcomings. As early as 2016, Dan Abramov wrote a long article explaining why mixins should be considered harmful. In short, three main factors were then identified as crucially detrimental:
- Implicit dependencies,
- Namespace clashing,
- Snowballing complexity.
Let’s discuss them shortly to see why they could be truly harmful. Firstly, implicit dependencies could lead to removing the needed functionality. Mixins are often based on other mixins - if we don’t have an explicit chain of inheritance, it’s hard to say what is really needed and what is there by chance. In fact, this is mixins’ main flaw - without a strict data flow structure, we are condemned to perform deep, recursive code research, as mixins are rarely independent. The next limitation related to mixins is somehow connected - the primary motivation to create mixins is to separate common and simple use cases, like handling modal opening. So, then mixins end up with named functions, for example “handleOpen()”, which is super common. So, if you’ll create a mixin for managing a modal state, and then for managing a tooltip state - you won’t be able to use them in the same component. The last shortcoming is pretty self-explaining I believe - mixins unlike components scale terribly. In most cases, they grow to the point where there is no one who really understands what a given mixin is actually doing. Therefore, if the mixins are far from flawless but, at the same time, they give a lot of possibilities for code reuse, they will still be used. That’s when the Composition API comes to your rescue.
Composition API
Let’s start with a complete example to see how exactly Composition API facilitates code-reusing. For ease of reference, this code snippet is just rewritten above the mixin example, providing the same functionality - namely simple pagination.
import { ref, toRefs } from '@vue';
export default function usePagination(options = {}) {
const { maxPages = ref(10), page = ref(1) } = toRefs(options);
const handlePageChange = fetchFunction => {
return type => {
if (type === 'next' && page < maxPages) {
page.value += 1;
fetchFunction();
} else if (type === 'prev' && page > 1) {
page.value -= 1;
fetchFunction();
}
}
}
return {
page,
maxPages,
handlePageChange,
}
}
If you’re familiar with React, you will start to notice the similarities between ReactJS and Vue 3, and you won’t be wrong because the below example looks like a react-hook implementation. If you haven’t used React Hooks before, you can read more about them here. Of course, there are some differences between ReactJS and Vue 3, but I’ll focus on them later. Coming back to our example, let’s see how to use composition API in the Vue component, comparing to mixins:
<script>
import { usePagination } from './use-pagination'
export default {
setup() {
const { page, maxPages, handlePageChange } = usePagination();
return {
page,
maxPages,
handlePageChange,
}
},
}
</script>
Here you can see how to use the provided mixin example:
<script>
import { paginationMixin } from './paginationMixin';
export default {
mixins: [paginationMixin],
};
</script>
Both these snippets come from Vue components. The first novelty here is the “setup()” function which is, in fact, a core part of the Composition API, and it serves as its entry point. Everything returned from here will be injected into the template and there is a different “this”. The first thing to note here is that we get rid of all the mixins’ flaws, just by using the Composition API! Now, let’s suppose that there is a name conflict. In such a case, you can just not destructure an object. Also, if the composition function is able to change component-internal properties, this prop must be explicitly passed to this function as an argument. As a result, there is no chance that it will change something, if it hadn’t been set up to do so. In short, there are no implicit dependencies. Finally, the third flaw also disappears, as it’s easier to keep SRP (Single Responsibility Principle).
However, replacing mixins is not the only usage of the Composition API. In fact, it replaces the Options API - sometimes fully, other times the changes are more cosmetic like, for example, the “Provide/Inject” functionality added in Vue 2.2. If so, how to use lifecycle methods which are part of Options? You can use them as hooks in the “setup()” function - just like everything else. Notice how the naming convention is slightly changed like in this example: mounted changes into onMounted.
You can see that the Composition API takes a lot from React Hooks, both in naming conventions and exposed logic, but there are some important differences. Those discrepancies result mainly from the differences in the approach to reactivity of both libraries. While the setup() function will be invoked only once during the component’s lifecycle, React Hooks will be invoked in each render. This results in a slightly different approach, for example, composition functions could be conditional, while the Hooks order must be always preserved.
Conclusion - a composition function or a hook?
Having the knowledge of how the new API works, we can answer the question concerning the original motivations and reasons to create it. I picked the specific mixin example not by chance. The reason was that it best reflects the direction of changes in frontend programming paradigms, which are going towards functional programming as well as code and logic reuse. The greatest advantage of such an approach is flexibility and the scaling potential. From now on, you don’t have to focus on how to separate repeated code and logic while trying to avoid bad practices like these resulting from using mixins. In fact, the Composition API solves such problems by using the proven solutions and patterns from React, meanwhile getting even closer to its tenets.
Recommended further reading:
Sources:
- Github: vuejs/rfcs
- When To Use The New Vue Composition API (And When Not To)
- VueJS - Quickstart
- How the Vue Composition API Replaces Vue Mixins
- VueJS - Using with Vue components
- VueJS - Fragments
- A Custom Renderer for Vue 3
- Vue 3 vs. Vue 2 Reactivity
So, what do you prefer - a composition function or a hook? Join us to discuss it!
Navigate the changing IT landscape
Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .