Preetish HS Freelance web developer, digital nomad, and design enthusiast. www.preetish.in

New features in Vue 3 and how to use them

7 min read 2129

The Vue logo in front of a blue background.

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.

Setup

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.

We made a custom demo for .
No really. Click here to check it out.

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.

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:

  1. Composition API (Now built-in)
  2. Multiple root elements (Template syntax )
  3. Suspense
  4. Multiple V-models
  5. Better Reactivity
  6. Portals
  • Composition API (Now built-in)
  • Multiple root elements (Template syntax )
  • Suspense
  • Multiple V-models
  • Better reactivity
  • Portals

Composition API

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:

  • Better organization
  • Sharing/reusing the code

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.

Multiple root elements (template syntax )

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

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>

Multiple v-models

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>

Better reactivity

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.

Global mounting

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.

Portals

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.

Conclusion

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.

Experience your Vue apps exactly how a user does

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. https://logrocket.com/signup/

LogRocket is like a DVR for web 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 - .

Preetish HS Freelance web developer, digital nomad, and design enthusiast. www.preetish.in

Leave a Reply