DEV Community

Cover image for Vue 3 -New features, Breaking changes & a Migration path
Fotis Adamakis
Fotis Adamakis

Posted on

Vue 3 -New features, Breaking changes & a Migration path

Vue 3 is here and everyone is looking for a way to migrate and start using it as soon as possible. There are several new features but also a lot of work done to improve performance and bundle size under the hood that makes this version a real candidate for the best client-side framework out there. The catch? New syntax, deprecations, and some breaking changes might make your migration plan slightly harder than expected. Let’s dive in and see what you should expect.

Mounting

The first thing that you will encounter is the difference in initializing your app. In Vue 2 you have to use Vue constructor with a render function and the $mount method like this

    import Vue from 'vue'

    import App from './app.vue'

    const app = new Vue({
      render: (h) => h(App),
    }).$mount('#app')
Enter fullscreen mode Exit fullscreen mode

In Vue 3 this is simplified with a more elegant syntax

    import { createApp } from "vue";

    import App from "./App.vue";

    createApp(App).mount("#app");
Enter fullscreen mode Exit fullscreen mode

Fragments

In Vue 2, multi-root components were not supported. The solution was to enclose your code in a wrapper element.

    <!-- Layout.vue -->
    <template>
      <div>
        <header>...</header>
        <main>...</main>
        <footer>...</footer>
      </div>
    </template>
Enter fullscreen mode Exit fullscreen mode

In Vue 3, components now can have multiple root nodes. This enables eliminating wrapper elements and writing cleaner markup.

    <!-- Layout.vue -->
    <template>
      <header>...</header>
      <main>...</main>
      <footer>...</footer>
    </template>
Enter fullscreen mode Exit fullscreen mode

Teleport

A not so common problem but very difficult to solve is having part of your component mounted in a different position in DOM than the Vue component hierarchy.

A common scenario for this is creating a component that includes a full-screen modal. In most cases, you’d want the modal’s logic to live within the component, but the positioning of the modal quickly becomes difficult to solve through CSS, or requires a change in component composition.

This can now easily achieved with the use of the teleport feature like this

    app.component('app-modal', {
      template: `
        <button @click="isOpen = true">
            Open modal
        </button>

        <teleport to="body">
          <div v-if="isOpen" class="modal">
              I'm a teleported modal
          </div>
        </teleport>
      `,
      data() {
        return { 
          isOpen: false
        }
      }
    })
Enter fullscreen mode Exit fullscreen mode

You can still interact and pass props to it like being inside the component!

Emits

How you emit events hasn’t changed but you can declare the emits in your component like this

    export default {
      name: 'componentName',
      emits: ['eventName']
    }
Enter fullscreen mode Exit fullscreen mode

This is not mandatory but should be considered a best practice because it enables self-documenting code

Composition API

A very controversial topic when first introduced back in June 2019 was the new Function-based Component API. This is a lot different than the existing Options API and caused a lot of confusion on the first sight. The good thing is that the existing Options API is not deprecated and everything is purely additive for handling advanced use cases and mainly replace mixins usage that admittedly has caused a lot of problems in large scale applications.

The new Composition API was designed for logic organization, encapsulations, and reuse which makes it extremely flexible, performant (no component instances involved) and makes easier tracking the source of every component property.

A simple example of how a component is structured by using the new API is the following

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

    <script>
    import { reactive, computed } from 'vue'

    export default {
      setup() {
        const state = reactive({
          count: 0,
          double: computed(() => state.count * 2)
        })

        function increment() {
          state.count++
        }

        return {
          state,
          increment
        }
      }
    }
    </script>
Enter fullscreen mode Exit fullscreen mode

The main drawback is that it will require some extra time to get familiar with it which doesn’t really align with the easy learning curve that Vue 2 is known. The good thing is that you don’t need to rewrite your existing components using the new API and you don’t need to use it everywhere as well.
Read more about the new Composition API RFC

Functional Components

Functional components are deprecated. The main reason to use a functional component was performance which is now no longer relevant since the changes done under the hood in component instantiation and compilation make this difference insignificant. This change unfortunately will require some manual migration.

Scoped slots

A change that might be painful for you to refactor if you use them is the removal of scoped slots. They are now merged with slots.

    // Vue 2 Syntax
    this.$scopedSlots.header

    // Vue 3 Syntax
    this.$slots.header()
Enter fullscreen mode Exit fullscreen mode

Event Bus

$on, $once, and $off methods are removed from the Vue instance, so in Vue 3 it can’t be used to create an event bus. Vue docs recommend using mitt library. It’s tiny and has the same API as Vue 2.

Filters

In Vue 3 filters are removed! You can actually implement the same functionality in a small plugin but the fact that the pipe of the filter conflicts with the Javascript bitwise operator means that expressions with filters are not valid. That's why the recommendation is using a method instead.

    // Vue 2 Syntax
    {{ msg | format }}

    // Vue 3 Alternative
    {{ format(msg) }}
Enter fullscreen mode Exit fullscreen mode

The drawback of this is that chaining multiple methods is not that elegant as chaining multiple filters but that’s a small price to pay.

    // Vue 2 Syntax
    msg | uppercase | reverse | pluralize
    // Vue 3 Alternative
    pluralize(reverse(uppercase(msg)))
Enter fullscreen mode Exit fullscreen mode

IE11 support

IE11 is not supported from the main bundle. If you are unlucky enough to have to support it, you will have to include some additional files with your bundle to polyfill things like proxies that are used from Vue 3.

Vuex

Vuex 4 has also released to accompany Vue 3. The API remains the same and the code will be compatible with the previous version. Disappointed? You shouldn’t be! That’s one less thing to migrate and with Vuex 5 just around the corner be sure that changes are coming. Removal of Mutations and nested modules only to name a few.
Read more about the proposal for Vuex 5

Migration plan to Vue 3

  1. Read the official migration guide

  2. Replace Event bus usages with mitt library

  3. Update scoped slots to be regular slots

  4. Replace filter with methods

  5. Upgrade to Vue 2.7 — This version will have deprecation warnings for every feature that is not compatible with Vue 3 and will guide you with documentation links on how to handle every case.

  6. Upgrade to Vue 3

Just have in mind that this will probably be a long process and might take up to one year, depending on your project size and the deprecated features you are currently using. It might not be your first priority but given the massive performance improvement and the elegant new Composition API, this is definitely worth it!

Top comments (0)