1 Understanding Vue 3 and Creating Components

Vue 3 brings a lot of new features and changes for developers, all of them designed to aid development and improve the framework's overall stability, speed, and maintainability. Using other frameworks and libraries as inspiration, the Vue core team managed to achieve a great level of abstraction on the API where anyone can use Vue now, irrespective of whether they're a frontend developer or a backend developer.

In this chapter, we will learn how to upgrade our Vue project to the new version, and more about some of the new Vue features, such as the multiple root elements, the new attribute inheritance engine, how we can use the exposed reactivity API outside of Vue in another application, and how to create a component using the new composition API.

In this chapter, you will learn the following:

  • What is new in Vue 3

  • Upgrading your Vue 2 application to Vue 3

  • Creating components with multiple root elements

  • Creating components with attribute inheritance

  • Using the reactivity and observable API outside the scope of Vue

  • Creating a component using the composition API

What is new in Vue 3

You may be wondering how a new version of a framework could result in such hype on the internet? Imagine taking a car on the highway, doing a complete 360 roll, and then continuing to go full speed ahead in the same direction. This would cause a theatrical scene, and it's the perfect way to describe how Vue will go from version 2 to 3.

In this first part of the chapter, I will introduce you to the improvements on Vue, what was added to the framework, what has changed, and how it will impact the way you code a Vue application.

Improvements to the framework

There are numerous improvements to the Vue framework in this new release; all of them focused on making the framework better in every way possible. Here are some of the improvements that can impact the everyday development and usage of the framework by users and developers.

Under the hood

The outer shell looks the same as the old one, but the engine is a piece of art. In the new version, there is no leftover code from Vue 2. The core team built the framework from the ground up using TypeScript and rewrote everything geared to the maximum performance of the framework.

TypeScript was chosen to create a more maintainable code base for the Vue core team and the open-source community, and to improve the autocomplete features, such as IntelliSense or typeahead that the IDEs and code editors provide, without the need for special plugins and extensions.

Render engine

For Vue 3, a new render engine was developed using a new algorithm for the shadow DOM. This new render is totally exposed by the core of the framework by default, without the need to be executed by the framework. This makes it possible for new implementations of a completely new render function that can be injected into the framework and replace the original render engine.

In this new version of Vue, a new template compiler was written from scratch. This new compiler uses a new technique for cache manipulation and to manage the rendered elements, and a new hoisted method is applied to the creation of VNodes.

For cache manipulation, a new method is applied to control the position of the element, where the element can be a dynamic element with computed data or a response to a function that can be mutated.

The Vue core team has made an explorer where it's possible to see how the new template compiler renders the final render function. This can be viewed at https://vue-next-template-explorer.netlify.app/.

Exposed APIs

With all these modifications, it was possible to render all the Vue APIs exposed to usage within files outside the scope of application of Vue. It's possible to use the Vue reactivity or the shadow DOM in a React application, without the need to render a Vue application inside the React application. This explosibility is a way of transforming Vue into a more versatile framework, where it can be used anywhere, not just in frontend development.

New custom components

Vue 3 introduces three new custom components that can be used by the developer to resolve old problems. These components were present on Vue 2 but as third-party plugins and extensions. Now they are made by the Vue core team and added to the Vue core framework.

Fragments

In Vue 2, we always needed to have a parent node wrapping the components inside the single-file components. This was caused by the way in which the render engine of Vue 2 was constructed, requiring a root element on each node.

In Vue 2, we needed to have a wrapper element, encapsulating the elements that will be rendered. In the example, we have a div HTML element, wrapping two p HTML child elements, so we can achieve multiple elements on the page:

<template>
<div>
<p>This is two</p>
<p>children elements</p>
</div>
</template>

Now, in Vue 3, it's possible to declare any number of root elements on the single-file components without the need for special plugins using the new Fragments API, which will handle the multiple root elements. This helps to maintain a cleaner final code for the user, without the need for empty shells just for wrapping elements:

<template>
<p>This is two</p>
<p>root elements</p>
</template>

As we saw in the Vue 3 code, we were able to have two root p HTML elements, without the need for a wrapper element.

Teleport

A Teleport component, also known as a Portal component, as the name implies, is a component that can make an element go from one component to another. This may seem strange in the first instance, but it has a lot of applications, including dialogs, custom menus, alerts, badges, and many other customs UIs that need to appear in special places.

Imagine a header component, where you want a custom slot on the component so you can place components:

<template>
<header>
<div id="blue-portal" />
</header>
</header>

Then, you want to display a custom button on this header, but you want to call this button from a page. You just need to execute the following code:

<template>
<page>
<Teleport to="blue-portal">
<button class="orange-portal">Cake</button>
</Teleport>
</page>
</template>

Now, your button will be displayed on the header, but the code will be executed on the page, giving access to the page scope.

Suspense

When the wait for the data is taking longer than you would like, how about showing a custom loader for the user? This is now possible without the need for custom code; Vue will handle this for you. The Suspense component will manage this process, with a default view once the data is loaded, and a fallback view when the data is being loaded.

You can write a special wrapper like this:

<template>
<Suspense>
<template #default>
<data-table />
</template>
<template #fallback>
<loading-gears />
</template>
</Suspense>
</template>

The new Vue composition API will understand the current state of your component, so it will be able to differentiate if the component is loading or if it's ready to be displayed.

API changes

Some API changes were made in Vue 3 that were necessary in order to clean the Vue API and simplify development. Some of them are break changes, and others are additions. But don't worry; the Vue 2 object development was not removed, it's still there, and will continue to be used. This declaration method was one of the reasons why many developers choose Vue over other frameworks.

There are some break changes that will happen in Vue 3 that are important to learn more about. We will discuss the most important break changes that will be introduced in Vue 3, and how to deal with then.

In Vue 3, a new way of creating the components is being introduced – the composition API. This method will make the maintainability of your code better, and give you a more reliable code, where you will have the full power of TypeScript available.

Some minor break changes

There are some minor break changes that are present in Vue 3 that need to be mentioned. These changes relate to one method we used previously to write code, and that has now been replaced when using Vue 3. It's not a Herculean job, but you need to know about them.

Goodbye filters, hello filters! The Vue filters API

The way we used filters on Vue 2 is no longer available. The Vue filter has been removed from the API. This change was made to simplify the render process and make it faster. All filters, in the end, are functions that receive a string and return a string.

In Vue 2, we used to use filters like this:

{{ textString | filter }}

Now, in Vue 3, we just need to pass a function to manipulate the string:

{{ filter(textString) }}

The bus just left the station! The event bus API

In Vue 2, we were able to use the power of the global Vue object to create a new Vue instance, and use this instance as an event bus that could transport messages between components and functions without any hassle. We just needed to publish and subscribe to the event bus, and everything was perfect.

This was a good way to transfer data between components, but was an anti-pattern approach for the Vue framework and components. The correct way to transfer data between components in Vue is via a parent-child communication, or state management, also known as state-driven architecture.

In Vue 3, the $on, $off, and $once instance methods were removed. To use an event bus strategy now, it is recommended to use a third-party plugin or framework such as mitt (https://github.com/developit/mitt).

No more global Vue – the mounting API

In Vue 2, we were accustomed to importing Vue, and prior to mounting the application, use the global Vue instance to add the plugins, filters, components, router, and store. This was a good technique where we could add anything to the Vue instance without needing to attach anything to the mounted application directly. It worked like this:

import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';

Vue.use(Vuex);
const store = new Vuex.store({});

new Vue({
store,
render: (h) => h(App),
}).$mount('#app');

Now, in Vue 3, this is no longer possible. We need to attach every component, plugin, store, and router to the mounted instance directly:

import { createApp } from 'vue';
import { createStore } from 'vuex';
import App from './App.vue';

const store = createStore({});

createApp(App)
.use(store)
.mount('#app');

Using this method, we can create different Vue applications in the same global application, without the plugins, store, or router of the applications messing with one another.

v-model, v-model, v-model – multiple v-model

When developing a single-file component, we were stuck with a single v-model directive and a .sync option for a second update change. This meant us using a lot of custom event emitters and huge object payloads to handle data inside the component.

In this breaking change, a collateral break change was introduced that resulted in the model property (https://vuejs.org/v2/api/#model) being removed from the Vue API. This property is used in custom components that used to do the same thing that the new v-model directive now does.

The new way to use the v-model directive will change how the sugar syntax works. In Vue 2, to use a v-model directive, we had to create a component expecting to receive the props as "value", and when there was a change, we needed to emit an 'input' event, like the following code:

<template>
<input
:value="value"
@input="$emit('input', $event)"
/>
</template>
<script>
export default {
props: {
value: String,
},
}
</script>

In Vue 3, to make the syntactic sugar work, the props property that the component will receive and the event emitter will change. Now, the component expects a props named modelValue and it emits an event, 'update:modelValue', like the following code:

<template>
<input
:modelValue="modelValue"
v-on:['update:modelValue']="$emit('update:modelValue', $event)"
/>
</template>
<script>
export default {
props: {
modelValue: String,
},
}
</script>

But how about the multiple v-model directives? Understanding the v-model break change is the first step in getting to know how the new method of multiple v-model will work.

To create multiple v-model components, we need to create various props with the name of the model directive we want and emit 'update:value' events where the value is the name of the model directive:

<script>
export default {
props: {
name: String,
email: String,
},
methods: {
updateUser(name, email) {
this.$emit('update:name', name);
this.$emit('update:email', email);
}
}
}
</script>

In the component where we want to use the multiple v-model directives, use the following code:

<template>
<custom-component
v-model:name="name"
v-model:email="email"
/>
</template>

The component will have each v-model directive, bounded to the event the child is emitting. In this case, the child component emits 'update:email' (the parent component) in order to be able to use the v-model directive with the email modifier. For example, you can use v-model:email to create the two-way data binding, between the component and the data.

Composition API

This is one of the most anticipated features of Vue 3. The composition API is a new way of creating Vue components, with an optimized way of writing code, and providing full TypeScript type checking support in your component. This method organizes the code in a simpler and more efficient way.

In this new way of declaring a Vue component, you just have a setup property that will be executed and will return everything your component needs in order to be executed, like this example:

<template>
<p @click="increaseCounter">{{ state.count }}</p>
</template>
<script>
import { reactive, ref } from 'vue';

export default {
setup(){
const state = reactive({
count: ref(0)
});

const increaseCounter = () => {
state.count += 1;
}

return { state, increaseCounter }
}
}
</script>

You will import the reactivity API from the Vue core to enable it in the object type data property, in this case, state. The ref API enables reactivity in the basic type value, like count, which is a number.

Finally, the functions can be declared inside the setup functions and passed down on the returned object. Then, everything is accessible in the <template> section.

Now, let's move on to some recipes.

Technical requirements

In this chapter, we will be using Node.js and Vue-CLI.

Attention Windows users! You need to install an NPM package called windows-build-tools to be able to install the following requisite packages. To do this, open Power Shell as an administrator and execute the following command:
> npm install -g windows-build-tools

To install Vue-CLI, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install -g @vue/cli @vue/cli-service-global

Creating the base file

In all recipes in this chapter, we will use this base template which we will create now. Make sure you follow these steps to create the file before starting the example in the recipe:

  1. Create a new .html file in any folder and open it.

  2. Create an html tag and add a head HTML element as a child. Inside the head HTML element, add a script HTML element with the src attribute defined as http://unpkg.com/vue@next:

<html>
<head>
<script src="https://unpkg.com/vue@next"></script>
</head>
</html>

  1. As a sibling of the  head HTML element, create a body HTML element. Inside the body HTML element, add a div HTML element with the attribute id defined as "app":

<body>
<div id="app">
</div>
</body>

  1. Finally, as a sibling of the div HTML element, create a script HTML element, with empty content. This will be where we will place the code for the recipes:

<script></script>

Upgrading your Vue 2 application to Vue 3

Upgrading your project from Vue 2 to Vue 3 can sometimes be done automatically, but in other cases, this needs to be done manually. This depends on how deep into the use of the Vue API you go with your application.

With projects made and managed by Vue-CLI, this process will be made seamlessly and will have a more straightforward approach compared to projects using a custom framework wrapper CLI.

In this recipe, you will learn how to upgrade your application using Vue-CLI and how to upgrade the project and the dependencies manually.

Getting ready

The prerequisite for this recipe is as follows:

  • Node.js 12+

The Node.js global objects that are required are as follows:

  • @vue/cli

  • @vue/cli-service-global

How to do it...

In order to upgrade your Vue 2 project to Vue 3, you will have to split the upgrade into different parts. We have the upgrade of the framework itself, and then we have the ecosystem components, such as vue-router and vuex, and finally, the bundler that joins everything in the end.

The framework upgrade comes with break changes. There are some break changes that are presented in this book in the What is new in Vue 3 section of this chapter, and others that may occur in a more advanced API schema. You have to manually update and check whether your components are valid for the upgrade on the framework.

Using Vue-CLI to upgrade the project

Using the latest version of Vue-CLI, you will be able to use Vue 3 in your project, out of the box, and you will be able to update your current project to Vue 3.

To update Vue-CLI to the latest version, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install @vue/cli-service@latest

Upgrading the project manually

To upgrade the project manually, you will have to first upgrade the project dependencies to their latest versions. You cannot use an old version of a Vue ecosystem plugin with Vue 3. To do this, perform the following steps:

  1. We need to upgrade the Vue framework, the ESLint plugin (which Vue depends on), and the vue-loader for the bundler. To upgrade it, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install vue@next eslint-plugin-vue@next vue-loader@next

  1. We need to add the new Vue single-file component compiler as a dependency to the project. To install it, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install @vue/compiler-sfc@latest

  1. If you are using unit tests and the @vue/test-utils package on your project, you will also need to upgrade this dependency. To upgrade it, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install @vue/test-utils@next @vue/server-test-utils@latest

  1. For the Vue ecosystem plugins, if you are using vue-router, you will need to upgrade this too. To upgrade it, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install vue-router@next

  1. If your application is using vuex as the default state management, you will need to upgrade this too. To upgrade it, you need to open Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install vuex@next

Changing the starting files

With the new version of the packages, we will need to change our starting files. In a Vue project that was created with the Vue-CLI starter kit, you will find a file named main.js or main.ts. If you are using TypeScript, this file is located in the src folder. Now follow these instructions:

  1. Open the main.js file in the src folder of your project. At the top of the file, where the packages are imported, you will see the following code:

import Vue from 'vue';

We need to change this to the new Vue exposed API method. To do this, we need to import createApp from the Vue package as follows:

import { createApp } from 'vue';

  1. Remove the global Vue static attribute definition of Vue.config.productionTip from your code.

  2. The mounting function of your application needs to be changed. The old API will look like this:

new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');

The old API should be changed to the new createApp API, as follows:

createApp(App)
.use(router)
.use(store)
.mount('#app')

  1. Open your vuex store instantiation file (normally, this file is located in src/store and is named store.js or index.js).

  2. Change the creation of the store from the instantiation of a new vuex class to the new createStore API. The vuex v3 class instantiation may look like this:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
state: { /* ... */ },
mutations: { /* ... */ },
actions: { /* ... */ },
getters: { /* ... */ },
modules: { /* ... */ },
});

You need to replace its content with the createStore API, which could look like this, for example:

import { createStore } from 'vuex';

export default createStore({
state: { /* ... */ },
mutations: { /* ... */ },
actions: { /* ... */ },
getters: { /* ... */ },
modules: { /* ... */ },
});

  1. In the vue-router ecosystem, we will need to replace the old API from the router creation with the new one. To do this, open the router creation file (in the src/router folder, normally named router.js or index.js).

  2. Finally, in the creation file, replace the old vue-router class instantiation with the new createRouter API. The vue-router v3 class instantiation may look like this:

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

export default new VueRouter({
routes: [{
path: '/',
name: 'HomePage',
component: () => import('pages/home'),
}]
});

You will also need to replace the new VueRouter instantiation with the new createRouter and createWebHistory API, as in this example:

import {
createRouter,
createWebHistory,
} from 'vue-router';

Vue.use(VueRouter);

export default createRouter({
history: createWebHistory(),
routes: [{
path: '/',
name: 'HomePage',
component: () => import('pages/home'),
}]
});

How it works...

In the upgrading process, Vue has provided us with two ways to update our project. The first way is to use the Vue-CLI plugin, which tries to automate almost all the processes and changes needed for the upgrade.

The second way is to upgrade the project manually. This method requires the developer to upgrade all the dependencies to the latest version, install the new single-file component compiler, @vue/compiler-sfc, and change the entry files for the Vue application, router, and store to the new API.

Following the changes to the starter structure of the project, the developer needs to check the components to see whether there are any Vue 3 breaking changes present, refactor the component to the new Vue 3 APIs, and remove the deprecated APIs from Vue 2.

Creating components with multiple root elements

In Vue 3, it is possible to create components with multiple root elements, without the need for a wrapping element. This option is also known as a fragment.

In React, this has been possible for a long time, but in Vue, you need to use custom third-party plugins such as vue-fragment (https://github.com/Thunberg087/vue-fragment) to use this feature.

In this recipe, you will learn how to create a component with multiple root elements, and how it could be used with a <template> section and a render function.

How to do it...

In this recipe, we will create two examples of a multiple root element component, one with a <template> structure, and another with a render function. To do this, this recipe will be divided into two parts.

Creating the component with the <template> structure

In order to use the <template> structure in our example, we will be using the template property of the Vue object where we can pass a string or a template string as the value, which will be interpolated by the Vue script and rendered on the screen:

  1. Using the base example from the 'Creating the base file' section, create a new file named template.html and open it.

  2. In the empty <script> HTML element, create the constants defineComponent and createApp by object-destructuring the Vue global constant:

const {
defineComponent,
createApp,
} = Vue;

  1. Create a constant named component, defined as the defineComponent method, passing a JavaScript object as an argument with three properties: data, methods, and template:

const component = defineComponent({
data: () => ({}),
methods: {},
template: ``
});

  1. In the data property, define it as a singleton function, returning a JavaScript object, with a property named count and with the default value as 0:

data: () => ({
count: 0
}),

  1. In the methods property, create a property called addOne, which is a function that will increase the value of count by 1:

methods: {
addOne() {
this.count += 1;
},
},

  1. In the template property, in the template string, create an h1 HTML element with a title. Then, as a sibling, create a button HTML element with an event listener bound to the click event, triggering the addOne function when executed:

template: `
<h1>
This is a Vue 3 Root Element!
</h1>
<button @click="addOne">
Pressed {{ count }} times.
</button>
`

  1. Finally, call the createApp function, passing the component constant as an argument. Then, prototype chain the mount function and, as an argument of the function, pass the div HTML element id attribute, ("#app"):

createApp(component)
.mount('#app');

Creating the component with the render function

In order to use the <template> structure in our example, we will be using the template property of the Vue object, where we can pass a string or a template string as the value, which will be interpolated by the Vue script and rendered on the screen:

  1. Using the base example from the 'Creating the base file' section, create a new file named render.html and open it.

  1. In the empty <script> HTML element, create the constants of the functions that will be used using the object destructuring method, calling the defineComponent, h, and createApp methods from the Vue global constant:

const {
defineComponent,
h,
createApp,
} = Vue;

  1. Create a constant named component, defined as the defineComponent method, passing a JavaScript object as an argument with three properties: data, methods, and render:

const component = defineComponent({
data: () => ({}),
methods: {},
render() {},
});

  1. In the data property, define it as a singleton function, returning a JavaScript object with a property named count and with the default value as 0:

data: () => ({
count: 0
}),

  1. In the methods property, create a property called addOne, which is a function that will increase the value of count by 1:

methods: {
addOne() {
this.count += 1;
},
},

  1. In the render property, perform the following steps:

    • Create a constant named h1 and define it as the h function, passing 'h1' as the first argument, and the title that will be used as the second argument.

    • Create a constant named button, which will be the h function, passing "button" as the first argument, a JavaScript object with the property onClick with a value of this.addOne as the second argument, and the content of button as the third argument.

    • Return an array, with the first value as the h1 constant, and the second value as the button constant:

render() {
const h1 = h('h1', 'This is a Vue 3 Root Element!');
const button = h('button', {
onClick: this.addOne,
}, `Pressed ${this.count} times.`);

return [
h1,
button,
];
},

  1. Finally, call the createApp function, passing the component constant as an argument, prototype chaining the mount function, and passing the div HTML element id attribute, ("#app"), as an argument of the function:

createApp(component)
.mount('#app');

How it works...

The new Vue component creation API needs to be executed by a function, defineComponent, and the JavaScript object that is passed as an argument maintains almost the same structure as the old structure in Vue 2. In the examples, we used the same properties, data, render, methods, and template, all present in Vue 2.

In the example with the <template> structure, we didn't have to create a wrapper element to encapsulate the content of our application component and were able to have two root elements on the component directly.

In the render function example, the same behavior occurs, but the final example used the new exposed h API, where it is no longer a parameter of the render function. A breaking change was present in the example; in the button creation, we had to use the onClick property inside the data JavaScript object, not the on property, with the click method. This happens because of the new data structure of the VNode of Vue 3.

Creating components with attribute inheritance

Since Vue 2, it has been possible to use attribute inheritance on components, but in Vue 3, attribute inheritance was made better and with a more reliable API to use in the components.

Attribute inheritance in components is a pattern that provides faster development of custom components based on HTML elements (such as custom inputs, buttons, text wrappers, or links).

In this recipe, we will create a custom input component with attribute inheritance applied directly to the input HTML element.

How to do it...

Here, we will create a component that will have a full attribute inheritance on a selected element on the DOM tree:

  1. Using the base example from the Creating the base file section, create a new file named component.html and open it.

  2. In the empty <script> HTML element, create the constants of the functions that will be used using the object destructuring method, calling the defineComponent and createApp methods from the Vue global constant:

const {
defineComponent,
createApp,
} = Vue;

  1. Create a constant named nameInput, defined as the defineComponent method, passing a JavaScript object as an argument with four properties: name, props, template, and inheritAttrs. Then, we define the value of inheritAttrs as false:

const nameInput = defineComponent({
name: 'NameInput',
props: {},
inheritAttrs: false,
template: ``
});

  1. In the props property, add a property called modelValue and define it as String:

props: {
modelValue: String,
},

  1. In the template property, within the template string, we need to do the following:

    • Create a label HTML element and add an input HTML element as a child.

    • In the input HTML element, define the v-bind directive as a JavaScript object with the destructed value of this.$attrs.

    • Define the variable attribute value as the received prop's modelValue.

    • Set the input attribute type as "text".

    • To the change event listener, add an anonymous function, which receives an event as the argument, and then emit an event called "update:modeValue" with the payload event.target.value:

template: `
<label>
<input
v-bind="{
...$attrs,
}"
:value="modelValue"
type="text"
@change="(event) => $emit('update:modelValue',
event.target.value)"
/>
</label>`

  1. Create a constant named appComponent, defined as the defineComponent method, passing a JavaScript object as an argument with two properties, data and template:

const component = defineComponent({
data: () => ({}),
template: ``,
});

  1. In the data property, define it as a singleton function, returning a JavaScript object with a property named name, with the default value as '':

data: () => ({
name: ''
}),

  1. In the template property, within the template string, we need to do the following:

    • Create a NameInput component with a v-model directive bounded to the name data property.

    • Create a style attribute with the value "border:0; border-bottom: 2px solid red;".

    • Create a data-test attribute with the value "name-input":

template: `
<name-input
v-model="name"
style="border:0; border-bottom: 2px solid red;"
data-test="name-input"
/>`

  1. Create a constant named app, and define it as the createApp function, passing the component constant as the argument. Then, call the app.component function, passing as the first argument the name of the component you want to register, and as the second argument the component. Finally, call the app.mount function, passing "#app" as the argument:

const app = createApp(component);
app.component('NameInput', nameInput);
app.mount('#app');

How it works...

In Vue 3, in order to create a component, we need to execute the defineComponent function, passing a JavaScript object as an argument. This object maintains almost the same component declaration structure as Vue 2. In the examples, we used the same properties, data, methods, props, and template, all present in the V2.

We used the inheritAttrs property to block the auto application of the attributes to all elements on the components, applying them just to the element with the v-bind directive and with the this.$attrs object deconstructed.

To register the component in the Vue application, we first created the application with the createApp API and then executed the app.component function to register the component globally on the application, prior to rendering our application.

Using the reactivity and observable API outside the scope of Vue

In Vue 3, with the exposed APIs, we can use the Vue reactivity and reactive variables without the need to create a Vue application. This enables backend and frontend developers to take full advantage of the Vue reactivity API within their application.

In this recipe, we will create a simple JavaScript animation using the reactivity and watch APIs.

How to do it...

Here, we will create an application using the Vue exposed reactivity API to render an animation on the screen:

  1. Using the base example from the 'Creating the base file' section, create a new file named reactivity.html and open it.

  2. In the <head> tag, add a new <meta> tag with the attribute chartset defined as "utf-8":

<meta charset="utf-8"/>

  1. In the <body> tag, remove the div#app HTML element, and create a div HTML element with the id defined as marathon and the style attribute defined as "font-size: 50px;":

<div
id="marathon"
style="font-size: 50px;"
>
</div>

  1. In the empty <script> HTML element, create the constants of the functions that will be used using the object destructuring method, calling the reactivity and watch methods from the Vue global constant:

const {
reactive,
watch,
} = Vue;

  1. Create a constant named mod, defined as a function, which receives two arguments, a and b. This then returns an arithmetic operation, a modulus b:

const mod = (a, b) => (a % b);

  1. Create a constant named maxRoadLength with the value 50. Then, create a constant named competitor with the value as the reactivity function, passing a JavaScript object as the argument, with the position property defined as 0 and speed defined as 1:

const maxRoadLength = 50;
const competitor = reactive({
position: 0,
speed: 1,
});

  1. Create a watch function, passing an anonymous function as the argument. Inside the function, do the following:

    • Create a constant named street, and define it as an Array with a size of maxRoadLength, and fill it with '_'.

    • Create a constant named marathonEl, and define it as the HTML DOM node, #marathon.

    • Select the element on the street in the array index of competitor.position and define it as "" if the competitor.position number is even, or "" if the number is odd.

    • Define marathonEl.innertHTML as "" and street.reverse().join(''):

The emojis used in this recipe are Person Running and Person Walking. The emoji image may vary depending on your OS. The images presented in this recipe are the emojis for the Apple OS.
watch(() => {
const street = Array(maxRoadLength).fill('_');
const marathonEl = document.getElementById('marathon');
street[competitor.position] = (competitor.position % 2 === 1)
? ''
: '';

marathonEl.innerHTML = '';
marathonEl.innerHTML = street.reverse().join('');
});

  1. Create a setInterval function, passing an anonymous function as the argument. Inside the function, define competitor.position as the mod function, passing competitor.position plus competitor.speed as the first argument, and maxRoadLength as the second argument:

setInterval(() => {
competitor.position = mod(competitor.position +competitor.speed,
maxRoadLength)
}, 100);

How it works...

Using the exposed reactive and watch APIs from Vue, we were able to create an application with the reactivity present in the Vue framework, but without the use of a Vue application.

First, we created a reactive object, competitor, that works in the same way as the Vue data property. Then, we created a watch function, which works in the same way as the watch property, but is used as an anonymous function. In the watch function, we made the road for the competitor to run on, and created a simple animation, using two different emojis, changing it based on the position on the road, so that it mimics an animation on the screen.

Finally, we printed the current runner on the screen and created a setInterval function of every 100ms to change the position of the competitor on the road:

Creating a component using the composition API

The composition API is a new way to write Vue components, based on the use of functions to compose the component, and it makes the organization and reusability of the code better.

This method is inspired by React Hooks and introduces the technique of creating a special function to compose the applications that can be shared without the need to be inside the Vue application because of the use of the exposed Vue APIs.

In this recipe, we will learn how to create an external function that fetches the user's geolocation and displays that data on the screen using the composition API.

How to do it...

Here, we will create a component using the composition API, which will fetch the user GPS position and show that information on the screen:

  1. Using the base example from the 'Creating the base file' section, create a new file named component.html and open it.

  2. In the empty <script> HTML element, create the constants of the functions that will be used using the object destructuring method, calling the createApp, defineComponent, setup, ref, onMounted, and onUnmounted methods from the Vue global constant:

const {
createApp,
defineComponent,
setup,
ref,
onMounted,
onUnmounted,
} = Vue;

  1. Create a fetchLocation function and, inside this, create a let variable named watcher. Then, create a constant named geoLocation and define it as navigator.geolocation. Next, create a constant named gpsTime and define it as the ref function, passing the Date.now() function as the argument. Finally, create a constant named coordinates and define it as the ref function, passing a JavaScript object as the argument, with the properties accuracy, latitude, longitude, altitude, altitudeAccuracy, heading, and speed defined as 0:

function fetchLocation() {
let watcher;
const geoLocation = navigator.geolocation;
const gpsTime = ref(Date.now());
const coordinates = ref({
accuracy: 0,
latitude: 0,
longitude: 0,
altitude: 0,
altitudeAccuracy: 0,
heading: 0,
speed: 0,
});
}

  1. Then, inside the fetchLocation function, following the creation of the constants, create a function named setPosition with a parameter named payload. Inside the function, define gpsTime.value as the payload.timestamp argument and coordinates.value as the payload.coords argument:

function setPosition(payload) {
gpsTime.value = payload.timestamp
coordinates.value = payload.coords
}

  1. Following creation of the setPosition function, call the onMounted function, passing an anonymous function as the argument. Inside the function, check whether the browser has the geoLocation API available, and define watcher as the geoLocation.watchPostion function, passing the setPosition function as the argument:

onMounted(() => {
if (geoLocation) watcher = geoLocation.watchPosition(setPosition);
});

  1. After calling the onMounted function, create an onUnmounted function passing an anonymous function as the argument. Inside the function, check whether watcher is defined and then execute the geoLocation.clearWatch function, passing watcher as the argument:

onUnmounted(() => {
if (watcher) geoLocation.clearWatch(watcher);
});

  1. Finally, in the fetchLocation function, return a JavaScript object, and as the properties/values define, pass the coordinates and gpsTime constants:

return {
coordinates,
gpsTime,
};

  1. Create a constant named appComponent and define it as the defineComponent function, passing a JavaScript object with the properties setup and template as the argument:

const appComponent = defineComponent({
setup() {},
template: ``
});

  1. In the setup function, create a constant, which is an object destructuring with the properties coordinates and gpsTime of the fetchLocation function:

setup() {
const {
coordinates,
gpsTime,
} = fetchLocation();
}

  1. Inside the setup function, create another constant named formatOptions, and define it as a JavaScript object with the properties yearmonthdayhour, and minute as 'numeric'. Then, define the property hour12 as true:

const formatOptions = {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true,
};

  1. Following the creation of the formatOptions constant, create a constant named formatDate and define it as a function, which receives a parameter named date. Then, return a new Intl.DateTimeFormat function, passing navigator.language as the first argument, and the formatOption constant as the second argument. Then, prototype chain the format function, passing the date parameter:

const formatDate = (date) => (new 
Intl.DateTimeFormat(navigator.language,
formatOptions).format(date));

  1. Finally, at the end of the setup function, return a JavaScript object with the properties defined as coordinatesgpsTime, and formatDate constants:

return {
coordinates,
gpsTime,
formatDate
};

  1. In the template property, do the following:

    • Create an h1 HTML element with the text "My Geo Position at {{ formatDate(new Date(gpsTime) }}".

    • Create a ul HTML element and add three li HTML elements as children.

    • In the first child element, add the text "Latitude: {{ coordinates.latitude }}".

    • In the second child element, add the text "Longitude: {{ coordinates.longitude }}".

    • In the third child element, add the text "Altitude: {{ coordinates.altitude }}":

template: `
<h1>My Geo Position at {{formatDate(new
Date(gpsTime))}}</h1>
<ul>
<li>Latitude: {{ coordinates.latitude }}</li>
<li>Longitude: {{ coordinates.longitude }}</li>
<li>Altitude: {{ coordinates.altitude }}</li>
</ul>
`

  1. Finally, call the createApp function, passing the appComponent constant as an argument. Then, prototype chain the mount function, and, as an argument of the function, pass the div HTML element id attribute, ("#app"):

createApp(appComponent)
.mount('#app');

How it works...

In this recipe, first, we imported the exposed APIs - createApp,  defineComponentsetuprefonMounted, and onUnmounted, – as constants, which we will use to create the component. Then, we created the fetchLocation function, which has the responsibility of getting the user's geolocation data and returning it as reactive data that can be automatically updated when the user changes their location.

The ability to fetch the user GPS positions was possible because of the navigator.geolocation API present on modern browsers, which are able to fetch the user's current GPS position. Using this data provided by the browser, we were able to use it to define the variables created with the Vue ref APIs.

We created the component using the setup function of the Vue object declaration, so the rendering knows that we are using the new composition API as the component creation method. Inside the setup function, we imported the dynamic variables of the fetchLocation function and created a method that formats the date to use as a filter on the template.

Then we returned the imported variables and the filter, so they can be used on the template section. In the template section, we created a title adding the time of the last GPS position, used the filter to format it, and created a list of the user's latitude, longitude, and altitude.

Finally, we created the application using the createApp exposed API and mounted the Vue application.

See also

You can find more information about Navigator.geolocation at https://developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation.

You can find more information about Intl.DateTimeFormat at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.199.122