Interesting news Vue 3

Instead of the foreword



Vue is used in all FunCorp projects. We carefully monitor the development of the framework, constantly improve the development process and implement best practices. And, of course, we could not pass by and not translate the article by Philip Rakovsky, co-founder of VueStorefront, about new Vue 3 features that seriously affect code writing.



image

Last time, we looked at features that affect the performance of Vue 3 . We already know that applications written on the new version of the framework work very quickly, but performance is not the most important change. For most developers, how Vue 3 affects the way you write code is much more important.



As you might have guessed, Vue 3 will have a lot of cool features. Fortunately, the Vue team has added more enhancements and additions than breaking changes. Because of this, most developers who know Vue 2 should quickly become comfortable with the new syntax.



Let's start with an API that many of you may have heard about.



Composition API



The Composition API is the most talked about and mentioned feature of the next major version of Vue. The Composition API syntax provides a completely new approach to organizing and reusing code.



We are now creating components with a syntax called the Options API. In order to add logic, we create properties (options) in the component object, for example data, methods, computed, etc. The main disadvantage of this approach is that it is not JavaScript code as such. You need to know exactly what options are available in the template and what this will be like. The Vue compiler converts properties into working JavaScript code for you. Due to this feature, we cannot fully use auto-completion or type checking.



The Composition API solves this problem and makes it possible to use the mechanisms available through options using ordinary JavaScript functions.

The Vue team describes the Composition API as "an optional, feature-based API that allows flexible use of composition in component logic." Code written using the new API is easier to read, making it easier to understand.



To understand how the new syntax works, consider an example of a simple component.



<template> <button @click="increment"> Count is: {{ count }}, double is {{ double }}, click to increment. </button> </template> <script> import { ref, computed, onMounted } from 'vue' export default { setup() { const count = ref(0) const double = computed(() => count.value * 2) function increment() { count.value++ } onMounted(() => console.log('component mounted!')) return { count, double, increment } } } </script>
      
      





We will break the code into parts and analyze what is happening here.



 import { ref, computed, onMounted } from 'vue'
      
      





As I mentioned above, the Composition API presents the component options as functions, therefore, first of all, we must import the necessary functions. In this example, we need to create a reactive property using ref calculated by computed and access the mounted lifecycle hook using the onMounted function.



You may have a question: what is this mysterious setup method?



 export default { setup() {} }
      
      





In short, setup is just a function that passes properties and functions to a template. We describe all the reactive and computed properties, lifecycle hooks and all observers in the setup function, and then return them to use in the template.



To the fact that we will not return from setup, there will be no access in the template.



 const count = ref(0)
      
      





The count reactive property is initialized using the ref function. It takes a primitive or object and returns a reactive link. The passed value will be stored in the value property of the created link. For example, if we want to access the value of count, we need to explicitly access count.value.



 const double = computed(() => count.value * 2) function increment() { count.value++ }
      
      





So we declare a computed double property and an increment function.



 onMounted(() => console.log('component mounted!'))
      
      





Using the onMounted hook, we print a message to the console after mounting the component to demonstrate this possibility.



 return { count, double, increment }
      
      





So that the count and double properties and the increment method are available in the template, we return them from the setup method.



 <template> <button @click="increment"> Count is: {{ count }}, double is {{ double }}. Click to increment. </button> </template>
      
      





And voila! We have access to the properties and methods from setup, just as if they were declared through the old Options API.



This is a simple example, similar could easily be written using the Options API.

But the advantage of the new Composition API is not so much in the ability to write code in a different style, but in the possibilities opened up for reusing logic.



Reusing code with the Composition API



Let's take a closer look at the benefits of the new Composition API, for example, for reusing code. Now, if we want to use some piece of code in several components, we have two options: mixins and scoped slots. Both options have their drawbacks.



We want to extract the functionality of the counter and reuse it in other components. Here is an example of how this can be done using the existing and using the new API.



First, let's look at an implementation using mixins.



 import CounterMixin from './mixins/counter' export default { mixins: [CounterMixin] }
      
      





The biggest problem with this approach is that we don’t know anything about what is being added to our component. This makes understanding difficult and may lead to conflicts with existing properties and methods.



Now consider slots with a limited scope.



 <template> <Counter v-slot="{ count, increment }"> {{ count }} <button @click="increment">Increment</button> </Counter> </template>
      
      





When using slots, we know exactly what properties we have access to through the v-slot directive, which is quite simple to understand. The disadvantage of this approach is that we can only access the data of the Counter component.



Now consider an implementation using the Composition API.



 function useCounter() { const count = ref(0) function increment () { count.value++ } return { count, incrememt } } export default { setup () { const { count, increment } = useCounter() return { count, increment } } }
      
      





It looks much more elegant, right? We are not limited by either a template or scope, and we know for sure which counter properties are available. And due to the fact that useCounter is just a function that returns data, as a pleasant bonus we get code completion in the editor. There is no magic here, so the editor can help us with type checking and give hints.



Using third-party libraries also looks better. For example, if we want to use Vuex, we can explicitly import the useStore function and not clog the Vue prototype with the this. $ Store property. This approach allows you to get rid of additional manipulations in plugins.



 const { commit, dispatch } = useStore()
      
      





If you want to know more about the Composition API and its applications, I recommend reading a document in which the Vue team explains the reasons for creating a new API and offers cases in which it will be useful. There is also a wonderful repository with examples of using the Composition API from Thorsten Lünborg, one of the members of the Vue core team.



Configuration and Mounting Changes



There are other important changes in the new Vue in the way we build and configure our application. Let's look at an example.



 import Vue from 'vue' import App from './App.vue' Vue.config.ignoredElements = [/^app-/] Vue.use(/* ... */) Vue.mixin(/* ... */) Vue.component(/* ... */) Vue.directive(/* ... */) new Vue({ render: h => h(App) }).$mount('#app')
      
      





We are now using the Vue global object to configure and create new Vue instances. Any change we make to the Vue object will affect the final instances and components.



Let's see how this will work in Vue 3.



 import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.config.ignoredElements = [/^app-/] app.use(/* ... */) app.mixin(/* ... */) app.component(/* ... */) app.directive(/* ... */) app.mount('#app')
      
      





As you already noticed, the configuration refers to a specific Vue instance created using createApp.



This makes our code more readable, reduces the possibility of unexpected problems with third-party plugins. Now, any third-party library that modifies the global Vue object can affect your application in an unexpected place (especially if it is a global mixin), which is not possible in Vue 3.



These changes are discussed in the RFC, and perhaps in the future the implementation will be different.



Fragments



Another cool feature we can count on in Vue 3.

What are fragments?

Currently, a component can have only one root element, which means that the code below will not work.



 <template> <div>Hello</div> <div>World</div> </template>
      
      





The reason is that the Vue instance hiding behind each component can only be attached to one DOM element. Now there is a way to create a component with several root elements: for this you need to write a component in a functional style that does not need its own Vue instance.



It turns out that the same problem exists in the React community, it was solved using the virtual Fragment element.



It looks like this:



 class Columns extends React.Component { render() { return ( <React.Fragment> <td>Hello</td> <td>World</td> </React.Fragment> ); } }
      
      





Despite the fact that Fragment looks like a regular DOM element, it is virtual and will not be created in the DOM tree. With this approach, we can use the functionality of a single root element without creating an extra element in the DOM.



Now you can use fragments in Vue 2, but using the vue-fragments library, and in Vue 3 they will work out of the box!



Suspense



Another great idea from the React ecosystem that will be implemented in Vue 3 is Suspense.



Suspense pauses component rendering and displays a stub until certain conditions are met. At Vue London, Ewan Yu casually touched Suspense and revealed the API we can expect in the future. Suspense component will have 2 slots: for content and for stub.



 <Suspense> <template > <Suspended-component /> </template> <template #fallback> Loading... </template> </Suspense>
      
      





The stub will be displayed until the <Suspended-component /> component is ready. The Suspense component can also expect to load the asynchronous component or perform some asynchronous actions in the setup function.



Multiple v-models



v-model is a directive with which you can use two-way binding. We can pass the reactive property and change it inside the component.



We are well known for working with form elements.



 <input v-model="property" />
      
      





But did you know that v-model can be used with any component? Under the hood, v-model is just forwarding the value parameter and listening for the input event.



You can rewrite the previous example using this syntax as follows:



 <input v-bind:value="property" v-on:input="property = $event.target.value" />
      
      





You can even change the default property names and events using the model option:



 model: { prop: 'checked', event: 'change' }
      
      





As you can see, the v-model directive can be a very useful “syntactic sugar” if we want to use two-way binding in our components. Unfortunately, there can be only one v-model per component.



Fortunately, in Vue 3 this problem will be solved. We can pass the name to v-model and use as much v-model as necessary.



Usage example:



 <InviteeForm v-model:name="inviteeName" v-model:email="inviteeEmail" />
      
      





These changes are discussed in the RFC, and perhaps in the future the implementation will be different.



Portals



Portals are components created for rendering content outside the hierarchy of the current component. This is also one of the features implemented in React . In the React documentation, portals are described as follows: "Portals allow you to render children in a DOM node that is outside the DOM hierarchy of the parent component."



Portals are great for implementing components such as modal windows, popups, and all those that need to be displayed on top of the page.



When using portals, you can be sure that the styles of the parent component will not affect the child component. It will also save you from dirty z-index hacks.



For each portal, we need to specify the destination in which the portal content should be displayed.



The following is an implementation option on the portal-vue library, which adds portals to Vue 2.



 <portal to="destination"> <p>This slot content will be rendered wherever the portal-target with name 'destination' is located.</p> </portal> <portal-target name="destination"> <!-- This component can be located anywhere in your App. The slot content of the above portal component wilbe rendered here. --> </portal-target>
      
      





And in Vue 3, this feature will be out of the box.



New Custom Directive API



The custom directive API will change a bit in Vue 3 to better match the component's life cycle. Creating directives will become more intuitive, and therefore easier for beginners to understand and learn.



Now the custom directive declaration looks like this:



 const MyDirective = { bind(el, binding, vnode, prevVnode) {}, inserted() {}, update() {}, componentUpdated() {}, unbind() {} }
      
      





And in Vue 3 it will look like this:



 const MyDirective = { beforeMount(el, binding, vnode, prevVnode) {}, mounted() {}, beforeUpdate() {}, updated() {}, beforeUnmount() {}, // new unmounted() {} }
      
      





Although these are breaking changes, they can be used with a compatible Vue build.



This API is also discussed and may change in the future.



Summary



Next to the significant innovation - the Composition API - we can find several smaller improvements. Obviously, Vue is moving towards improving the developer experience, towards simplifying and intuitive APIs. It is also cool to see that the Vue team decided to add a lot of ideas to the core of the framework that are already implemented in third-party libraries.



The list above contains only the most important API improvements and changes. If you would like to learn about others, check out the RFC repository .



All Articles