What is new in Vue 3
You may be wondering how a new version of a framework could result in such hype on the internet? Imagine taking a car on the highway, doing a complete 360 roll, and then continuing to go full speed ahead in the same direction. This would cause a theatrical scene, and it's the perfect way to describe how Vue will go from version 2 to 3.
In this first part of the chapter, I will introduce you to the improvements on Vue, what was added to the framework, what has changed, and how it will impact the way you code a Vue application.
Improvements to the framework
There are numerous improvements to the Vue framework in this new release; all of them focused on making the framework better in every way possible. Here are some of the improvements that can impact the everyday development and usage of the framework by users and developers.
Under the hood
The outer shell looks the same as the old one, but the engine is a piece of art. In the new version, there is no leftover code from Vue 2. The core team built the framework from the ground up using TypeScript and rewrote everything geared to the maximum performance of the framework.
TypeScript was chosen to create a more maintainable code base for the Vue core team and the open-source community, and to improve the autocomplete features, such as IntelliSense or typeahead that the IDEs and code editors provide, without the need for special plugins and extensions.
Render engine
For Vue 3, a new render engine was developed using a new algorithm for the shadow DOM. This new render is totally exposed by the core of the framework by default, without the need to be executed by the framework. This makes it possible for new implementations of a completely new render function that can be injected into the framework and replace the original render engine.
In this new version of Vue, a new template compiler was written from scratch. This new compiler uses a new technique for cache manipulation and to manage the rendered elements, and a new hoisted method is applied to the creation of VNodes.
For cache manipulation, a new method is applied to control the position of the element, where the element can be a dynamic element with computed data or a response to a function that can be mutated.
The Vue core team has made an explorer where it's possible to see how the new template compiler renders the final render function. This can be viewed at https://vue-next-template-explorer.netlify.app/.
Exposed APIs
With all these modifications, it was possible to render all the Vue APIs exposed to usage within files outside the scope of application of Vue. It's possible to use the Vue reactivity or the shadow DOM in a React application, without the need to render a Vue application inside the React application. This explosibility is a way of transforming Vue into a more versatile framework, where it can be used anywhere, not just in frontend development.
New custom components
Vue 3 introduces three new custom components that can be used by the developer to resolve old problems. These components were present on Vue 2 but as third-party plugins and extensions. Now they are made by the Vue core team and added to the Vue core framework.
Fragments
In Vue 2, we always needed to have a parent node wrapping the components inside the single-file components. This was caused by the way in which the render engine of Vue 2 was constructed, requiring a root element on each node.
In Vue 2, we needed to have a wrapper element, encapsulating the elements that will be rendered. In the example, we have a div HTML element, wrapping two p HTML child elements, so we can achieve multiple elements on the page:
<template>
<div>
<p>This is two</p>
<p>children elements</p>
</div>
</template>
Now, in Vue 3, it's possible to declare any number of root elements on the single-file components without the need for special plugins using the new Fragments API, which will handle the multiple root elements. This helps to maintain a cleaner final code for the user, without the need for empty shells just for wrapping elements:
<template>
<p>This is two</p>
<p>root elements</p>
</template>
As we saw in the Vue 3 code, we were able to have two root p HTML elements, without the need for a wrapper element.
Teleport
A Teleport component, also known as a Portal component, as the name implies, is a component that can make an element go from one component to another. This may seem strange in the first instance, but it has a lot of applications, including dialogs, custom menus, alerts, badges, and many other customs UIs that need to appear in special places.
Imagine a header component, where you want a custom slot on the component so you can place components:
<template>
<header>
<div id="blue-portal" />
</header>
</header>
Then, you want to display a custom button on this header, but you want to call this button from a page. You just need to execute the following code:
<template>
<page>
<Teleport to="blue-portal">
<button class="orange-portal">Cake</button>
</Teleport>
</page>
</template>
Now, your button will be displayed on the header, but the code will be executed on the page, giving access to the page scope.
Suspense
When the wait for the data is taking longer than you would like, how about showing a custom loader for the user? This is now possible without the need for custom code; Vue will handle this for you. The Suspense component will manage this process, with a default view once the data is loaded, and a fallback view when the data is being loaded.
You can write a special wrapper like this:
<template>
<Suspense>
<template #default>
<data-table />
</template>
<template #fallback>
<loading-gears />
</template>
</Suspense>
</template>
The new Vue composition API will understand the current state of your component, so it will be able to differentiate if the component is loading or if it's ready to be displayed.
API changes
Some API changes were made in Vue 3 that were necessary in order to clean the Vue API and simplify development. Some of them are break changes, and others are additions. But don't worry; the Vue 2 object development was not removed, it's still there, and will continue to be used. This declaration method was one of the reasons why many developers choose Vue over other frameworks.
There are some break changes that will happen in Vue 3 that are important to learn more about. We will discuss the most important break changes that will be introduced in Vue 3, and how to deal with then.
In Vue 3, a new way of creating the components is being introduced – the composition API. This method will make the maintainability of your code better, and give you a more reliable code, where you will have the full power of TypeScript available.
Some minor break changes
There are some minor break changes that are present in Vue 3 that need to be mentioned. These changes relate to one method we used previously to write code, and that has now been replaced when using Vue 3. It's not a Herculean job, but you need to know about them.
Goodbye filters, hello filters! The Vue filters API
The way we used filters on Vue 2 is no longer available. The Vue filter has been removed from the API. This change was made to simplify the render process and make it faster. All filters, in the end, are functions that receive a string and return a string.
In Vue 2, we used to use filters like this:
{{ textString | filter }}
Now, in Vue 3, we just need to pass a function to manipulate the string:
{{ filter(textString) }}
The bus just left the station! The event bus API
In Vue 2, we were able to use the power of the global Vue object to create a new Vue instance, and use this instance as an event bus that could transport messages between components and functions without any hassle. We just needed to publish and subscribe to the event bus, and everything was perfect.
This was a good way to transfer data between components, but was an anti-pattern approach for the Vue framework and components. The correct way to transfer data between components in Vue is via a parent-child communication, or state management, also known as state-driven architecture.
In Vue 3, the $on, $off, and $once instance methods were removed. To use an event bus strategy now, it is recommended to use a third-party plugin or framework such as mitt (https://github.com/developit/mitt).
No more global Vue – the mounting API
In Vue 2, we were accustomed to importing Vue, and prior to mounting the application, use the global Vue instance to add the plugins, filters, components, router, and store. This was a good technique where we could add anything to the Vue instance without needing to attach anything to the mounted application directly. It worked like this:
import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';
Vue.use(Vuex);
const store = new Vuex.store({});
new Vue({
store,
render: (h) => h(App),
}).$mount('#app');
Now, in Vue 3, this is no longer possible. We need to attach every component, plugin, store, and router to the mounted instance directly:
import { createApp } from 'vue';
import { createStore } from 'vuex';
import App from './App.vue';
const store = createStore({});
createApp(App)
.use(store)
.mount('#app');
Using this method, we can create different Vue applications in the same global application, without the plugins, store, or router of the applications messing with one another.
v-model, v-model, v-model – multiple v-model
When developing a single-file component, we were stuck with a single v-model directive and a .sync option for a second update change. This meant us using a lot of custom event emitters and huge object payloads to handle data inside the component.
The new way to use the v-model directive will change how the sugar syntax works. In Vue 2, to use a v-model directive, we had to create a component expecting to receive the props as "value", and when there was a change, we needed to emit an 'input' event, like the following code:
<template>
<input
:value="value"
@input="$emit('input', $event)"
/>
</template>
<script>
export default {
props: {
value: String,
},
}
</script>
In Vue 3, to make the syntactic sugar work, the props property that the component will receive and the event emitter will change. Now, the component expects a props named modelValue and it emits an event, 'update:modelValue', like the following code:
<template>
<input
:modelValue="modelValue"
v-on:['update:modelValue']="$emit('update:modelValue', $event)"
/>
</template>
<script>
export default {
props: {
modelValue: String,
},
}
</script>
But how about the multiple v-model directives? Understanding the v-model break change is the first step in getting to know how the new method of multiple v-model will work.
To create multiple v-model components, we need to create various props with the name of the model directive we want and emit 'update:value' events where the value is the name of the model directive:
<script>
export default {
props: {
name: String,
email: String,
},
methods: {
updateUser(name, email) {
this.$emit('update:name', name);
this.$emit('update:email', email);
}
}
}
</script>
In the component where we want to use the multiple v-model directives, use the following code:
<template>
<custom-component
v-model:name="name"
v-model:email="email"
/>
</template>
The component will have each v-model directive, bounded to the event the child is emitting. In this case, the child component emits 'update:email' (the parent component) in order to be able to use the v-model directive with the email modifier. For example, you can use v-model:email to create the two-way data binding, between the component and the data.
Composition API
This is one of the most anticipated features of Vue 3. The composition API is a new way of creating Vue components, with an optimized way of writing code, and providing full TypeScript type checking support in your component. This method organizes the code in a simpler and more efficient way.
In this new way of declaring a Vue component, you just have a setup property that will be executed and will return everything your component needs in order to be executed, like this example:
<template>
<p @click="increaseCounter">{{ state.count }}</p>
</template>
<script>
import { reactive, ref } from 'vue';
export default {
setup(){
const state = reactive({
count: ref(0)
});
const increaseCounter = () => {
state.count += 1;
}
return { state, increaseCounter }
}
}
</script>
You will import the reactivity API from the Vue core to enable it in the object type data property, in this case, state. The ref API enables reactivity in the basic type value, like count, which is a number.
Finally, the functions can be declared inside the setup functions and passed down on the returned object. Then, everything is accessible in the <template> section.
Now, let's move on to some recipes.