Building a simple Applications with Vue 3

Vue.js 3 has not officially been released yet, but the maintainers have released experimental “alpha” versions. These cannot be used in production apps, but are great for you to try out and learn some of the new features.

The only problem is that the Vue docs haven’t been updated for version 3, so it takes a bit of digging around to figure out exactly what’s different.

In this article, I’ll walk you through the creation of a simple app using an alpha version of Vue 3, with the specific intention of highlighting new features.

Note that this article is current for alpha version 8. Some of the syntax shown could change in later versions.

Should you wait for the v3 release before learning Vue?

If you’re new to Vue.js, you’ll need to start by learning version 2, as that is presumed knowledge for this tutorial.

However, don’t feel like you’re wasting your time learning Vue 2 when Vue 3 is on the horizon. While some APIs will be added or changed, almost all of the key features and patterns of Vue 2 will still be used in Vue 3.

Table of contents:

  • Vue 3 installation and setup
  • Creating a new Vue app
  • Adding state properties
  • Using a root component
  • Creating a template
  • Composition API

Vue 3 installation and setup

Rather than installing Vue 3 directly, let’s clone the project vue-next-webpack-preview which will give us a minimal Webpack setup including Vue 3.

$ git clone https://github.com/vuejs/vue-next-webpack-preview.git vue3-experiment
$ cd vue3-experiment
$ npm i

Once that’s cloned and the NPM modules are installed, all we need to do is remove the boilerplate files and create a fresh main.js file so we can create our Vue app from scratch.

$ rm -rf src/*
$ touch src/main.js

Now we’ll run the dev server:

$ npm run dev

Creating a new Vue app

Straight off the bat, the way we bootstrap a new Vue app has changed. Rather than using new Vue(), we now need to import the new createApp method.

We then call this method, passing our Vue instance definition object, and assign the return object to a variable app.

Next, we’ll call the mount method on app and pass a CSS selector indicating our mount element, just like we did with the $mount instance method in Vue 2.

main.js

import { createApp } from "vue";

const app = createApp({
  // root instance definition
});

app.mount("#app");

Reason for change

With the old API, any global configuration we added (plugins, mixins, prototype properties etc) would permanently mutate global state. For example:

main.js

// Affects both instances
Vue.mixin({ ... })

const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })

This really shows up as an issue in unit testing, as it makes it tricky to ensure that each test is isolated from the last.

Under the new API, calling createApp returns a fresh app instance that will not be polluted by any global configuration applied to other instances.

Adding state properties

In this tutorial, we’ll create a classic “counter” app that allows a user to click a button to increment a displayed integer.

So let’s add a new state property count which we’ll give an initial value of 0.

Under Vue 2, we could do this by creating a data property on our app instance and assigning an object to this where our count property would be declared i.e.:

main.js

const app = createApp({
  data: {
    count: 0
  }
});

This is no longer allowed. Instead, data must be assigned a factory function which returns the state object.

This is what you had to do for Vue components, but now it’s enforced for Vue app instances as well.

main.js

const app = createApp({
  data: () => ({
    count: 0
  })
});

Reason for change

The advantage of using an object for data rather than a factory function is that, firstly, it was syntactically simpler, and secondly, you could share top-level state between multiple root instances e.g.:

main.js

const state = {
  sharedVal: 0
};

const app1 = new Vue({ state });
const app2 = new Vue({ state });

// Affects both instances
app1._data.sharedVal = 1;

The use case for this is rare and can be worked around. Since having two types of declarations is not beginner-friendly, it was decided to remove this feature.

Before we move on, let’s also add a method to increment the count value. This is no different from Vue 2.

main.js

const app = createApp({
  data: () => ({
    count: 0  
  }),
  methods: {
    inc() {
      this.count++;
    }
  }
});

Using a root component

If you go to the browser now and check the console, you’ll see the warning “Component is missing render function”, since we haven’t yet defined a template for the root instance.

The best practice for Vue 2 is to create a minimal template for the root instance and create an App component where the main app markup will be declared.

Let’s do that here, as well.

$ touch src/App.vue

Now we can get the root instance to render that component. The difference is that with Vue 2, we’d normally use a render function for doing this:

main.js

import App from "./App.vue";

const app = createApp({
  ...
  render: h => h(App)
});

app.mount("#app");

We can still do that, but Vue 3 has an even easier way - making App a root component. To do this, we can remove the root instance definition and instead pass the App component.

main.js

import App from "./App.vue";

const app = createApp(App);

app.mount("#app");

This means the App component is not just rendered by the root instance but is the root instance.

While we’re at it, let’s simply the syntax a little by removing the app variable:

main.js

createApp(App).mount("#app");

Moving over to the root component now, let’s re-add the state and method to this component:

App.vue

<script>
export default {
  data: () => ({
    count: 0  
  }),
  methods: {
    inc() {
      this.count++;
    }
  }
};
</script>

Creating a template

Let’s now create a template for our root component. We’ll use a text interpolation to display the count, and we’ll add a button with a click handler to increment our counter.

App.vue

<template>
  <span>Button clicked {{ count }} times.</span>
  <button @click="inc">Inc</button>
</template>
<script>
...
</script>

Notice anything odd about this template? Look again. I’ll wait.

That’s right - there are two root elements. In Vue 3, thanks to a feature called fragments, it is no longer compulsory to have a single root element.

Composition API

The flagship feature of Vue 3 is the Composition API. This new API allows you to define component functionality using a setup function rather than with properties you add to the component definition object.

Let’s now refactor our App component to use the Composition API.

Before I explain the code, be clear that all we’re doing is refactoring - the functionality of the component not changed at all. Also note that the template is not changed as the Composition API only affects the way we define the component functionality, not the display.

App.vue

<template>
  <span>Button clicked {{ count }}</span>
  <button @click="inc">Inc</button>
</template>
<script>
import { ref } from "vue";
export default {
  setup () {
    const count = ref(0);
    const inc = () => {
      count.value++;
    };
    return {
      count,
      inc
    }
  }
};
</script>

setup method

Firstly, notice we import the ref function which allows us to define a reactive variable count. This variable is equivalent to this.count.

The inc method is just a plain JavaScript function. However, notice that to change the value of count in the method body, we need to change its sub-property value. That’s because reactive variables created using ref are wrapped in an object. This is necessary to retain their reactivity as they’re passed around.

Finally, we return count and increment from the setup method, as these are the values that get passed to the template when it’s rendered.

Reason for change

Keep in mind that the Composition API is not a change as it’s purely optional to use. The main motivation is to allow for better code organization and the reuse of code between components (as mixins are essentially an anti-pattern).

You’d be correct in thinking that refactoring the App component to use the Composition API is unnecessary. But, if this were a much larger component, or we needed to share its features with other components, that’s when you’d see its usefulness.

#vuejs #vue #javascript #webdev

Building a simple Applications with Vue 3
623.25 GEEK