Vue 3 isn’t officially released yet, but the Vue team has released the Alpha version for us developers to use some of the features that will be shipped with Vue 3.
At the time of writing this article, we have the (Alpha-10) version available to experiment with.
Though this isn’t ready to be used in production yet, it’s always good to learn new features in advance so that when the stable version is released, we can directly start using it or migrate the existing Vue 2 applications to version 3.0 to use the latest features.
We’ll use the WebPack-based setup.
To do this, clone this repository:
git clone https://github.com/vuejs/vue-next-webpack-preview.git vue-next cd vue-next
Now install the packages:
npm install
That’s it. We have a working Vue 3 project set up now.
To spin up the application, just run the following:
npm run dev
Open localhost:8080
in your browser, and you can see a simple counter application.
Open the package.json
file, you can see the Vue version here. At the time of writing this article, the version is 3.0.0-alpha.8
.
Open App.vue
and you’ll see the setup()
method, i.e. the Composition API already being used here. We might see some lint errors from Vue’s official plugin, eslint-plugin-vue
, because the linters are not yet updated to understand the new syntax.
Before we start coding, let’s go over the new features in Vue 3.
Vue 3 is faster, smaller in file size, and equipped with better TypeScript support. Some of the new features that we can discuss and learn to implement in this article include:
Composition API was launched as a plugin a few months back, so there is nothing new there, but in Vue 3 we don’t have to install it like a plugin anymore. Now, it’s built into the package and can be used out of the box without any additional setup.
There are two main advantages to using the Composition API:
Vue 3 will still support Options API, so if you think you don’t need composition API you can always use the traditional methods used in Vue 2.
If you are new to Composition API, here is how we can use it to implement a component:
<template> <div class="counter"> <p>count: {{ count }}</p> <p>NewVal (count + 2): {{ countDouble }}</p> <button @click="inc">Increment</button> <button @click="dec">Decrement</button> <p> Message: {{ msg }} </p> <button @click="changeMessage()">Change Message</button> </div> </template> <script> import { ref, computed, watch } from 'vue' export default { setup() { /* ---------------------------------------------------- */ let count = ref(0) const countDouble = computed(() => count.value * 2) watch(count, newVal => { console.log('count changed', newVal) }) const inc = () => { count.value += 1 } const dec = () => { if (count.value !== 0) { count.value -= 1 } } /* ---------------------------------------------------- */ let msg= ref('some text') watch(msg, newVal => { console.log('msg changed', newVal) }) const changeMessage = () => { msg.value = "new Message" } /* ---------------------------------------------------- */ return { count, inc, dec, countDouble, msg, changeMessage } } } </script>
And here’s the equivalent code in Options API:
<template> <div class="counter"> <p>count: {{ count }}</p> <p>NewVal (count + 2): {{ countDouble }}</p> <button @click="inc">Increment</button> <button @click="dec">Decrement</button> <p> Message: {{ msg }} </p> <button @click="changeMessage()">Change Message</button> </div> </template> <script> export default { data() { return { count: 0, msg: 'some message' } }, computed: { countDouble() { return this.count*2 } }, watch: { count(newVal) { console.log('count changed', newVal) }, msg(newVal) { console.log('msg changed', newVal) } }, methods: { inc() { this.count += 1 }, dec() { if (this.count !== 0) { this.count -= 1 } }, changeMessage() { msg = "new Message" } } } </script>
We can see that using Composition API allows us better organization by keeping the the code (state, methods, computed properties, watchers etc) of particular features together, which was not possible in Options API.
In the above example, the code for counter
and the code for changing a message
is clearly separated in Composition API.
As the component grows in size, organizing code becomes an important factor. Any new developer can easily understand the code without spending too much time analyzing all the lines of code.
Before, we could use Mixins to share the code. However, it was hard to keep track of states and methods in different components, and Mixins had the potential to overwrite the existing state or methods in our components if we weren’t careful.
Using the Composition API makes sharing the code much easier. We can factor out the code for a particular feature and use it in multiple places, as shown below:
//message.js import { ref, watch } from 'vue' export function message() { let msg = ref(123) watch(msg, newVal => { console.log('msg changed', newVal) }) const changeMessage = () => { msg.value = 'new Message' } return { msg, changeMessage } }
Using the shared code in our component
<template> <div class="counter"> <p>count: {{ count }}</p> <p>NewVal (count + 2): {{ countDouble }}</p> <button @click="inc">Increment</button> <button @click="dec">Decrement</button> <p>Message: {{ msg }}</p> <button @click="changeMessage()">change message</button> </div> </template> <script> import { ref, computed, watch } from 'vue' import { message } from './common/message' export default { setup() { let count = ref(0) const countDouble = computed(() => count.value * 2) watch(count, newVal => { console.log('count changed', newVal) }) const inc = () => { count.value += 1 } const dec = () => { if (count.value !== 0) { count.value -= 1 } } let { msg, changeMessage } = message() return { count, msg, changeMessage, inc, dec, countDouble } } } </script>
Refer to the official Composition API guide for more details.
In Vue 2, the template tag can only take one root element. Even if we had just two <p>
tags, we had to enclose them within a <div>
tag to get it working. Because of this, we had to change the CSS code as well in the parent component so that it looked as expected.
In Vue 3, this restriction is lifted. There is no need for a root element anymore.
We can use any number of tags directly inside the <template></template>
section:
<template> <p> Count: {{ count }} </p> <button @click="increment"> Increment </button> <button @click="decrement"> Decrement</button> </template>
Equivalent code in Vue 2:
<template> <div class="counter"> <p> Count: {{ count }} </p> <button @click="increment"> Increment </button> <button @click="decrement"> Decrement</button> </div> </template>
Suspense is a new feature that renders a default/fallback component until the main component fetches the data.
Sometimes we use async operations to fetch data from the server. Instead of handing the template with v-if
and then setting it back when we return the data, Suspense does it for us.
Suspense can be used for both parts of the template, or the whole template:
<template> <Suspense> <template #default> <div v-for="item in articleList" :key="item.id"> <article> <h2>{{ item.title }}</h2> <p>{{ item.body }}</p> </article> </div> </template> <template #fallback> Articles loading... </template> </Suspense> </template> <script> import axios from 'axios' export default { async setup() { let articleList = await axios .get('https://jsonplaceholder.typicode.com/posts') .then(response => { console.log(response) return response.data }) return { articleList } } } </script>
We all know that v-model is used for two-way binding. We mostly use it with form elements. Sometimes, we even use it with custom components.
Vue-2 allowed the use of only one v-model on a component. In Vue-3, we can bind any number of v-models to our custom components:
<template> <survey-form v-model:name="name" v-model:age="age"> </survey-form> </template> //SurveyForm.vue <template> <div> <label>Name: </label> <input :value="name" @input="updateName($event.target.value)" /> <label>Age: </label> <input :value="age" @input="updateAge($event.target.value)" /> </div> </template> <script> export default { props: { name: String, age: Number }, setup(props, { emit }) { const updateName = value => { emit('update:name', value) } const updateAge = value => { emit('update:age', +value) } return { updateName, updateAge } } } </script>
Vue 2 already had great reactivity, and you might not have come across any cases where you found that reactivity was lacking. However, there were a few cases where Vue 2 fell short.
Let’s revisit Vue 2 and see what those limitations were.
To demonstrate reactivity, we’ll use watchers to listen to one of the state variables and then modify it to see if the watchers
are triggered:
<template> <div class="hello" @click="test">test {{list }} {{ myObj }}</div> </template> <script> export default { name: "HelloWorld", data() { return { list: [1, 2], myObj: { name: "Preetish" } }; }, watch: { list: { handler: () => { console.log("watcher triggered"); }, deep: true } }, methods: { test() { this.list[2] = 4; this.myObj.last = "HS"; delete this.myObj.name; } } }; </script>
None of the above three modifications — such as adding a new item to an array based on the index, adding a new item to an object, or deleting an item from the object — is reactive in Vue-2. Hence watchers
won’t be triggered, or the DOM would be updated. We had to use the vue.set()
or vue.delete()
methods.
In Vue-3, these work directly without any helper functions:
export default { setup() { let list = ref([1, 2]) let a = ref(0) let myObj = ref({ name: 'Preetish' }) function myFun() { list.value[3] = 3 myObj.value.last = 'HS' delete myObj.value.name } return { myFun, list, myObj } } }
We can see that watcher
was triggered all four times in the Vue 3 setup.
When you open main.js
in the about
project, you’ll notice something different. We no longer use the Global Vue instance to install plugins and other libraries.
Instead, you can see createApp
method:
import { createApp } from 'vue' import App from './App.vue' const myApp = createApp(App) myApp.use(/* plugin name */) myApp.use(/* plugin name */) myApp.use(/* plugin name */) myApp.mount('#app')
The advantage of this feature is that it protects the Vue application from third party libraries/plugins we use which might override or make changes to the global instance — mostly by using Mixins.
Now with the createApp
method, we install those plugins on a particular instance and not the global object.
Portal is a feature where we can render a part of the code which is present in one component into a different component in a different DOM tree. There was a third-party plugin called portal-vue
that achieved this in Vue 2.
In Vue 3, portal will be built in and it is very easy to use.
Vue 3 will have a special tag called <Teleport>
, and any code enclosed within this tag will be ready to be teleported anywhere. The Teleport
tag takes a to
argument.
Let’s see this in action:
<Teleport to="#modal-layer"> <div class="modal"> hello </div> </Teleport>
Any code inside <Portal></Portal>
will be displayed in the target location mentioned.
<div id="modal-target"></div>
At the time of writing this article, <Teleport>
doesn’t work in the Alpha version mentioned above.
If you are planning to start your new project, you can still go ahead and use Vue 2 with a Composition API plugin and later migrate to Vue 3 as there won’t be any breaking changes other than the removal of filters.
Vue 3 will be packed with lots of new and amazing features. The integrated composition will make a significant impact on the development flow in future apps by providing an easy way to organize and share code with great TypeScript support.
The performance will be fine-tuned, and the size of the package is reduced even more in the new upcoming update.
Other features like Suspense, multiple v-models, etc will make development easier than before.
Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps, including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error and what state the application was in when an issue occurred.
Modernize how you debug your Vue apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
2 Replies to "New features in Vue 3 and how to use them"
Thank you
Can you do Vue3 with typescript as you did for Vue2 previously?