What’s New in Vue 3? The Most Interesting New Features [Examples]

Tosia Reznikava

What’s new in Vue 3? The most interesting new features - featured image

Vue.js is a growing open-source front-end framework for building single-page applications and user interfaces. Since its first release, Vue.js has been increasing its popularity and gaining new users thanks to its developer-friendly syntax, ease of use, and famously helpful documentation.

In September 2020, the third version of Vue.js was released, and in February 2022, it became the new default version. Vue 3 is smaller, easier to maintain, and has more handy features.

What Has Changed in Vue 3?

Here are some of the new features in Vue 3 which grabbed my attention.

Cta image

Composition API

One of the Vue.js concepts allows us to create large applications built out of smaller, reusable components. This allows us to easily manage the development process.

In Vue 2 we had three ways to reuse code:

Vue 2 Method 1) Mixins

Advantage:
  •  Can be organized by feature.
Limitations:
  • Conflict-prone: we can end up with some property name conflicts.
  • Unclear relationship how they interact.
  • Isn't easily reusable in case we want to configure the mixin to be able to use it across other components.

Vue 2 Method 2) Mixin factories

Mixin factories are functions that return a customized version of mixin and give us the possibility to modify such mixin by sending a configuration to it.

Advantage:
  • Easily reusable: so we can configure the code and have a more specific relationship of how mixins interact.
Limitations:
  • Cannot be dynamically generated because there’s no instance access at runtime.
  • Namespacing requires strong conventions.
  • They have implicit property additions: we have to look inside the mixin to figure out what properties it exposes.
  • Isn't easily reusable in case we want to configure the mixin to be able to use it across other components. 

Vue 2 Method 3) Scoped slots

They are special types of slots that work as a reusable template where we can pass data, instead of already-rendered elements.

Advantage:
  • Addresses just about every downside of mixins.
Limitations:
  • Your configuration ends up in your template, which ideally should contain what we want to render.
  • Scoped slots increase indentation in our template, which can decrease readability.
  • Exposed properties are only available in the template.
  • Since we’re using 3 components instead of 1, it’s a bit less performant.

As you see, we have plenty of options, but each of them has its limitations. Also in Vue 2, we could write components by organizing them only by components options: components, props, data, computed, methods, and lifecycle methods, so during this process, our stand-alone components could get bigger and bigger, and as a result, become less readable and hardly maintainable for future development. 

What is Composition API and How It Works

Vue 3 offers us a feature that allows us to organize components by logical concerns — this feature is called Composition API. It’s an optional feature that allows us to write components in another way using more advanced syntax, you have to remember that you can still use the regular one. 

Composition API gives the opportunity to reuse code between components.

Vue 2 organizing code with default Options API and reuse code with mixin:


<template>
  <div id="app">
    <div class="content">
      <h1>{ {title} }</h1>
	<label for="title">
	  <input type="text" name="title" v-model="inputVal" />
	</label>
	<button @click="addTitle" class="add-btn">Add</button>
	<button @click="clearInput">Clear</button>
      	<button @click="pressMe">Clear</button>
    </div>
  </div>
</template>

<script>
const exampleMixin = {
  data() {
    return {
      someInfo: "This is our mixin!"
    }
  },
  methods: {
    pressMe() {
      alert(this.someInfo);
    }
  }
}
export default {
  mixins: [exampleMixin],
  data() {
    return {
	inputVal: "",
	title: ""
    };
  },
  methods: {
    clearInput() {
	this.inputVal = "";
    },
    addTitle() {
	this.title = this.inputVal;
    }
  }
};
</script>

As you can see, in the regular syntax our code is organized by component options (data & methods).

Vue 3 organizing code with Composition API:


<template>
  <div id="app">
    <div class="content">
      <h1>{ {title} }</h1>
      <label for="title">
        <input type="text" name="title" v-model="inputVal" />
      </label>
      <button @click="addTitle" class="add-btn">Add</button>
      <button @click="clearInput">Clear</button>
    </div>
  </div>
</template>

<script>


import { ref } from "vue";

export default {
  setup() {
    const inputVal = ref("");
    const title = ref("");

    function clearInput() {
      this.inputVal = "";
    }

    function addTitle() {
      this.title = this.inputVal;
    }

    return { inputVal, title, clearInput, addTitle };
  }
};
</script>

The Composition API is another way that Vue 3 gives us to reuse code. This feature has advantages and limitations.

Advantages:
  • Writing less code, easier to pull a feature from your component into a function.
  • It builds on your existing skills since you’re already familiar with functions.
  • More flexible than Mixins and Scoped Slots
  • Intellisense, autocomplete, and typings already work in your code editor.
  • Good TypeScript support, so you’ll be able to generate new projects with the latest typeScript versions in Vue 3.
Limitations:
  • Requires learning a new low-level API to define composition functions.

Teleport

Earlier called Portal, Teleport allows us to decide under which parent element we can place a piece of HTML code to render. The main advantage of Teleport is that you can place elements anywhere in the DOM tree, so we don’t need to nest components or in some cases split the code into two components.

It accepts a property called “to” in which we can specify where we want to put our elements, the one condition being that the target element should exist before the component has mounted.

So in Vue 3, this should look like this:


<teleport to="#app">
  <div>Foo</div>
</teleport>

<teleport to="#app">
  <div>Bar</div>
</teleport>

will compile to:


<div id="app">
  <div>Foo</div>
  <div>Bar</div>
</div>

You can send elements from multiple sources. Also, we need to remember that the imported elements do not inherit styles.

Teleport is very useful when we work with elements that need to be set in a specific order in the DOM, for instance: modal windows, some kind of alerts, or dialogs.

Some of the components breaking changes

  • If we want to define events in a component that this component will emit to its parent, we can use a new option called “emit”, similar to existing props.
  • If we want to define an asynchronous component, we should use defineAsyncComponent function:

Vue 2


const  asyncComponent = () => import ('./AsyncComponent.vue')

Vue 3


const  asyncComponent = defineAsyncComponent(() => import ('./AsyncComponent.vue'))

Fragments

In Vue 2, we cannot have multiple root elements, this was one of the limitations in the second version and often emit warnings if we tried to create this. Now in Vue 3, we don’t need to wrap root elements in one main element — we can have multiple nodes in the root.

Vue 2

<template>
  <div>
    <header></header>
    <section></section>
  </div>
</template>

Vue 3

<template>
  <header></header>
  <section></section>
</template>

Filters were removed from Vue 3

Filters are no longer supported, instead, we can use computed properties or methods.

Vue 2


<template>
  <div id="app">
    <span>{ {  amount | usd  } }</span>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        amount: 10
      }
    },
    filters: {
      usd(val) {
        return val + '$'
      
      }
    }
  }
</script>

Vue 3

<template>
  <div id="app">
    <span>{ { usd } }</span>
  </div>
<template>

<script>
export default {
  data() {
    return {
      amount: 10
    }
  },
  computed: {
    usd() {
      return this.amount + '$'
    }
  }
}
</script>

Experimental Suspense feature

Vue 3 allows us to perform lazy loading components with the Suspense component. 

The <Suspense> component allows us to provide some fallback content while our user is waiting for the data, so we can see something else for instance spinner or some text.

<Suspense>
  <template #default>
    <!-- component/component which makes an asynchronous call -->
  </template>
  <template #fallback>
    <!-- Content to display when loading -->
    Loading...
  </template>
</Suspense>

Experimental state-driven CSS variables

CSS variables allow us to store a value in one place and then reuse it elsewhere in our CSS code — this is a nice way to have cleaner code.

In Vue 3, a single file component supports v-bind() function in <style> tag, so we can bind a value from the component state to any CSS property. This allows us to dynamically change the value of some CSS properties.

Vue 3

<template>
  <div id="app">
    <div class="content">
       <h1>Hello world!</h1>
       <button @click="modalToggle">Click me</button>
    </div>
    <div v-if="showModal" class="modal">
      <h2>Modal window</h2>
      <button class="close-btn" @click="modalToggle">Close the window</button>
    </div>
    <div class="shadow"></div>
 </div>
</template>

<script>
export default {
  data() {
    return {
      displayShadow: "none",
      showModal: false
    };
  },
  methods: {
    modalToggle() {
      this.showModal = !this.showModal;
      this.displayShadow = this.showModal ? "block" : "none";
    }
  }
};
</script>

<style> .shadow { display: v-bind(displayShadow); position: absolute; top: 0; left: 0; width: 100vw; height: 100vh; background: rgb(46 46 46 / 31%); z-index: 10; opacity: 1; } </style>

In the code above, you can see a simple example of using such a feature. We wanted to show and hide modal window shadow depending on modal is visible or not. So we bound the displayShadow value to the display property of the .shadow class to conditionally change its value in the modalToggle method which is responsible for opening and closing of the modal window.

Single file component <style scoped> changes

If we use scoped styles in SFC, it means that the CSS applies only to this current component. This feature can help developers provide more consistent custom CSS in single file component scoped styles.

  • v-bind function is now supported by <style> tag
  • Instead of >>> and /deep/ combinators which are deprecated now we can use ::v-deep() or its shorter version :deep().
  • If we don’t want that slotted component was affected both by parent component styles and child’s scoped styles we can use ::v-slotted()/:slotted() pseudo-element. So in that way child scoped styles do not affect the slot component.

Multiple v-models

In Vue.js we’re using v-model for two-way binding. The most popular way to use it is of course the form elements. In Vue 2, we can use only one v-model in a single component, but Vue 3 allows us to pass multiple v-models to our components by specifying their names. Let’s see how it’ll look.

Vue 2 

<template>
  <div id="app">
    <exampleForm v-model="person" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      person: {
        age: 20,
        name: "Jan"
      }
    };
  }
};
</script>

Vue 3

<template>
  <div id="app">
    <exampleForm v-model:age="person.age" v-model:name="person.name" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      person: {
        age: 20,
        name: "Jan"
      }
    };
  }
};
</script>

Lifecycle naming changes

The destroyed lifecycle was renamed to unmounted. The beforeDestroy was changed to beforeUnmount.

Lifecycle methods available in Vue 3:

Options API (default)

Composition API (hook inside setup)

beforeCreate

Not needed

created

Not needed

beforeMount

onBeforeMount

mounted

onMounted

beforeUpdate

onBeforeUpdate

updated

onUpdated

beforeUnmount

onBeforeUnmount

unmounted

onUnmounted

errorCaptured

onErrorCaptured

renderTracked

onRenderTracked

renderTriggered

onRenderTriggered

<template> render changes:

In the new version <template> tag will render its content only if it will have a special directive in it, other than that, it will be treated as a plain HTML element and will render into plain <template> tag.

Vue Dev Guide mockup

Are you thinking about adding Vue.js to your stack?

Download our comprehensive guide with all the insights, data, and case studies about Vue.js.



Summary

I’ve presented only a small piece of all the changes that Vue offers us in its new version. To learn more, I encourage you to take a look at the Official Vue Docs and of course our newest Vue.js report.

Tosia Reznikava avatar
Tosia Reznikava