Categories
JavaScript Mega-Guides Vue

Vue.js Mega-Guide

Spread the love

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at the basics of building a Vue app.

Script Tag

We can include the Vue.js framework with a script tag.

To do this we can write:

<script src='https://vuejs.org/js/vue.js'></script>

before our app code loads. The URL above has the development version, which has all the warnings but it’s not minified.

To include the production version, we can write:

<script src='https://vuejs.org/js/vue.min.js'></script>

To fix the version to 2.6.11, we can write:

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>

We can also import the module in our browser if we don’t need to support Internet Explorer as follows:

<script type="module">     
  import Vue from 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.esm.browser.js' 
</script>

The vue.esm.browser.js file is already optimized for browsers so it can be used without creating too many requests.

Example

To create our first app, we can create the index.html file as follows:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Parcel Sandbox</title>  
    <meta charset="UTF-8" />  
    <script src="https://vuejs.org/js/vue.js"></script>  
  </head> <body>  
    <div id="app"></div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then in src/index.js , we can add:

new Vue({  
  template: "<div>{{ 'hello' }}</div>",  
  el: "#app"  
});

Then we should see hello on the screen, since we have a div with the ID app , which is used by Vue.js to render our Vue instance.

The Vue.js compiler is responsible for converting the template into HTML.

In the template above, 'hello' is a JavaScript string, which was converted to HTML text when the Vue compiler runs.

We specified that the template has a div with the text 'hello' in it.

Different Builds of the Vue.js Framework

There are several different builds of the Vue.js framework. They’re:

  • Full — Builds that have the compiler and the runtime. That’s the one we used in the example above.
  • Compiler — Code that’s responsible for compiling template strings into JavaScript render functions
  • Runtime — Code that’s responsible for creating Vue instances, rendering and updating virtual DOM, etc.
  • UMD — A module that can be directly included with a script tag. It includes the runtime and the compiler
  • CommonJS — A module that can be used with older bundlers like Browserify or Webpack 1.
  • ES Module — New to version 2.6. It can be used directly by browsers via <script type='module'> or be used with bundlers like Webpack 2 or Rollup.

Runtime and Compiler

The runtime is used for creating the Vue instance via new Vue .

The compiler is for compiling templates specified by the template option.

This means that:

new Vue({  
  template: "<div>{{ 'hello' }}</div>",  
  el: "#app"  
});

needs the compiler and:

new Vue({  
  el: "#app",  
  render(h) {  
    return h("div", "hello");  
  }  
});

doesn’t.

Template syntax will be much more convenient when our apps get more complex.

When we’re using vue-loader or vueify , templates inside .vue files are precompiled into JavaScript, so we don’t need the compiler in the final bundle.

When we don’t need to use the compiler, then we should use the runtime only build since it’s 30% smaller.

We can include different builds depending on the bundler that we’re using. For example, in Parcel, we can write:

"alias": {       
  "vue" : "./node_modules/vue/dist/vue.common.js"     
}

in package.json .

Vue CLI

We can Vue-CLI automatically create our app and build it so we don’t have to worry about which bundles to choose.

To install it, we can run:

npm install -g @vue/cli

or:

yarn global add @vue/cli

We can then create a project with it by running:

vue create project

or run:

vue ui

to show the GUI to let us create a Vue project in our browser.

Also, we can run the Vue CLI directly with npx by running:

npx vue create project

Running vue create will present us with a wizard with various options like:

Vue CLI v3.11.0  
┌───────────────────────────┐  
│  Update available: 4.1.2  │  
└───────────────────────────┘  
? Please pick a preset: Manually select features  
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)  
>(*) Babel  
 ( ) TypeScript  
 ( ) Progressive Web App (PWA) Support  
 ( ) Router  
 ( ) Vuex  
 ( ) CSS Pre-processors  
 (*) Linter / Formatter  
 ( ) Unit Testing  
 ( ) E2E Testing

This screen is presented when we choose Manually Select Features . We can choose Default if we don’t want to choose anything from this screen.

To run the Vue UI, we can run:

npx vue ui

Then our browser will automatically go to http://localhost:8000/project/select, where we see a Create link to create a project.

We can then create a project by clicking Create a new project here once we clicked on the Create tab.

Characteristics of the Vue Instance

Each Vue.js app begins by defining a new Vue instance. The Vue constructor takes an options object that takes various properties.

We often use vm to refer to a Vue instance, where vm stands for ViewModel.

A Vue app roughly follows the Model-View-ViewModel pattern, where the ViewModel has the business logic of the app, View has the markup that users see, and Model has the data.

For example, we can define a Vue instance as follows:

const vm = new Vue({ });

Each Vue app consists of a root Vue instance and it’s created with new Vue . We can organize it in a tree for easier maintenance.

Data and Methods

The options object that pass into the Vue constructor can have data and methods.

For example, if we define a Vue instance as follows:

const vm = new Vue({  
  el: "#app",  
  data: { foo: "bar" }  
});

Then when we add:

console.log(vm.foo);

below our vm definition, we get 'bar' since data.foo has the value 'bar' .

In other words, if we have:

const data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});  
console.log(vm.foo === data.foo);

Then the console.log will log true .

When the data changes, the app will re-render with the new data.

If we create a new property in vm and set it as follows:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});  
vm.a = 1;

The app won’t re-render. On the other hand, if we write:

let data = { foo: "bar", a: 1 };  
const vm = new Vue({  
  el: "#app",  
  data  
});

Then the app will re-render. This means that we have to put our data that we want to render in the data field.

If we freeze the object that we pass to data with Object.freeze() , then the Vue app won’t re-render since properties can’t be changed, so new changes can’t propagate since they aren’t set in the object.

So if we have:

let data = { foo: "bar", a: 1 };  
Object.freeze(data);  
const vm = new Vue({  
  el: "#app",  
  data  
});

No changes can be made after the initial render since we froze data with Object.freeze .

The Vue instance also exposes a number of instance properties and methods.

They’re prefixed with the $ so that we know they’re part of the Vue instance.

$el

We have the $el property to get the DOM element that the Vue instance resides in.

For example, if we have:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});

console.log(vm.$el === document.getElementById("app"));

Then the console.log will log true since our Vue instance resides in an element with ID app .

$data

The $data property will get us the value of the data property that we set in the options object that we passed into the Vue constructor.

So if we have:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});

console.log(vm.$data === data);

Then we get true from the console.log since data references the same object as vm.$data since we set it as the value of the data property of the options object that we passed into the Vue constructor.

$watch

$watch is an instance method that lets us watch for changes in the data object that we set as the value of the options object.

For example, if we have:

let data = { foo: "bar" };  
const vm = new Vue({  
  el: "#app",  
  data  
});

vm.$watch("foo", (newValue, oldValue) => {  
  console.log(newValue, oldValue);  
});

in src/index.js and:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <input type="text" v-model="foo" />  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

in index.html . Then when we type in something to the input box, we should get some console.log output from:

vm.$watch("foo", (newValue, oldValue) => {  
  console.log(newValue, oldValue);  
});

This is because changes are constantly being watched in the data.foo property. v-model automatically updates the value for foo as we type into the box.

So as we’re typing, the changes to foo are being watched and it’s logged by the handler function that we passed into the $watch method.

Vue Instance Lifecycle Hooks

Each Vue instance goes through a lifecycle. The lifecycle events can be handled with functions, which are the lifecycle hooks.

For example, when a Vue instance is created, the created hook is called.

We can handle the created event emitted by Vue.js as follows:

new Vue({  
  data: {  
    foo: "bar"  
  },  
  created() {  
    console.log(`foo is ${this.foo}`);  
  }  
});

Then we should see foo is bar from the console.log . Once the Vue instance is created and the initial data is set, the created hook is called.

This is why we see such output from the console.log .

Note that we can’t use arrow functions for lifecycle hooks since we reference the properties from this , which is the Vue instance.

There’re also other lifecycle hooks like mounted , updated , destroyed .

Lifecycle Diagram

The full lifecycle diagram is below. It outlines the whole workflow for creating a new Vue instance.

The red rounded rectangles have the hooks that are called.

Courtesy of vuejs.org

Creation Hooks

Creation hooks are called when the Vue instance begins its initialization process. It lets us do things before the Vue instance is added to the DOM.

This hook is also run during server-side rendering

We don’t have access to this.$el (the target mounting element) or the DOM within this hook since no DOM manipulation is done at this point.

beforeCreate

The beforeCreate hook runs during initialization. data has been made reactive and events aren’t set up yet.

For example, if we have:

new Vue({  
  data: {  
    foo: "bar"  
  },  
  beforeCreate() {  
    console.log("beforeCreated called");  
  },  
  created() {  
    console.log(`foo is ${this.foo}`);  
  }  
});

Then we see:

beforeCreated called  
foo is bar

which confirms that beforeCreate is called before created .

created

data and events are available in the created hook since the Vue instance has been initialized.

For example, if we write:

new Vue({  
  data: {  
    foo: "bar"  
  },  
  created() {  
    this.foo = "baz";  
    console.log(`foo is ${this.foo}`);  
  }  
});

Then we get:

foo is baz

since we changed the value of this.foo in the created hook before logging its value.

Mounting Hooks

The mounting hooks are run when DOM manipulation is being done, including mounting the Vue instance to the specified element.

We can use it to access or modify the DOM of our component.

beforeMount

The beforeMount hook runs before the initial render happens and after the template or render functions are compiled.

It doesn’t get called during server-side rendering.

For instance, if we write:

new Vue({  
  el: "#app",  
  data: {  
    foo: "bar"  
  },  
  created() {  
    console.log(`created called`);  
  },  
  beforeMount() {  
    console.log(`beforeMount called`);  
  }  
});

Then we get:

created called  
beforeMount called

mounted

When the mounted hook is called, we have access to the component, template, and the DOM.

It’s used frequently for the initialization of our own data like fetching data over the network and integrate non-Vue libraries.

For example, if we have the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    foo: "bar"  
  },  
  beforeMount() {  
    console.log(this.$el.textContent);  
  }  
});

and the following in index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> 
  <body>  
    <div id="app">foo</div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then we get foo logged since we have foo in the div with ID app .

Updating Hooks

Updating hooks are called when a reactive property used by our component changes or we some triggers re-rendering manually.

They’re useful for checking when a component is re-rendering.

However, we should use computed properties or watchers to track reactive property changes.

beforeUpdate

beforeUpdate runs after data changes in our component, right before the DOM is updated and re-rendered.

It lets us get the new state before the DOM is re-rendered.

For example, if we have the following in src/index.html :

new Vue({  
  el: "#app",  
  data: {  
    foo: "bar"  
  },  
  beforeUpdate() {  
    console.log(this.foo);  
  },  
  methods: {  
    toggle() {  
      this.foo = this.foo === "bar" ? "baz" : "bar";  
    }  
  }  
});

and the following in index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{foo}}</p>  
      <button @click="toggle">Toggle</button>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then when we click the Toggle button, we can see the value changes logged in the console.log call of the beforeUpdate method.

updated

The updated hook runs after data changes on our Vue instance and the DOM re-renders.

For example, if we have the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    foo: "bar"  
  },  
  updated() {  
    console.log(this.$refs.foo.textContent);  
  },  
  methods: {  
    toggle() {  
      this.foo = this.foo === "bar" ? "baz" : "bar";  
    }  
  }  
});

and the following in index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p ref="foo">{{foo}}</p>  
      <button @click="toggle">Toggle</button>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then we can get the text content of the p element with ref set to foo by logging the value as follows:

console.log(this.$refs.foo.textContent);

in the updated hook and we can see the new value for it as we click the Toggle button.

Destruction Hooks

These hooks are run when the Vue instance is destroyed. Therefore, we can run clean up code in there.

These hooks aren’t run during server-side rendering.

beforeDestroy

beforeDestroy is run before teardown. Our Vue instance is still present and functional.

Therefore, we can run our clean up code in here.

For example, we can use it as follows:

new Vue({  
  el: "#app",  
  data: {  
    foo: "bar"  
  },  
  beforeDestroy() {  
    this.foo = null;  
  }  
});

destroyed

This is called after the Vue instance is destroyed. All the directives are unbound, event listeners are removed and child Vue instances are destroyed.

For example, we can use it as follows:

new Vue({  
  el: "#app",  
  data: {  
    foo: "bar"  
  },  
  destroyed() {  
    this.foo = null;  
  }  
});

Templates

Vue templates let us bind data to the DOM.

Vue compiles the template into Virtual DOM render functions. It can figure out the minimal number of components to re-render and minimize DOM manipulation when data changes.

Interpolations

The most basic form of data binding is text interpolation with double curly braces.

For example, we can write:

<p>{{foo}}</p>

To show the value of foo from a Vue instance or component.

The code above will be updated when foo updates.

We can keep it from updating after the first render by using the v-once directive as follows:

<p v-once>{{foo}}</p>

Raw HTML

We can also put raw HTML into an element with the v-html directive.

This lets us add HTML elements and formatting into an element.

For example, if we have the following in src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    rawHtml: "<b>bar</b>"  
  }  
});

and the following in index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> 
  <body>  
    <div id="app">  
      <p v-html="rawHtml"></p>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then we get bar bolded displayed since the inner HTML of the p element is <b>bar</b> .

When we’re using this, we have to sanitize the HTML so that it won’t contain any code. This is to prevent cross-site scripting attacks since code will be run if there are any when we pass in a value to the v-html directive.

Attributes

We can set values of HTML element attributes dynamically with v-bind as follows.

In src/index.js , we write:

new Vue({  
  el: "#app",  
  data: {  
    isButtonDisabled: true  
  }  
});

Then when we write the following in index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> 
  <body>  
    <div id="app">  
      <button v-bind:disabled="isButtonDisabled">Button</button>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then since we set the isButtonDisabled property to true , the button will be disabled. This also apply to any other truthy value.

On the other hand, if we set it to false , we see that the button is disabled. This also applies to other falsy values like null , undefined , 0 and empty string.

JavaScript Expressions

We can add any other JavaScript expressions between the curly braces.

For example, we can write:

{{ number + 10 }}  
{{ yes ? 'YES' : 'NO' }}  
{{ str.split('').reverse('').join('') }}

Also, we can write expressions as the value of the v-bind directive as follows:

<div v-bind:id="`div-${id}`"></div>

Statements will not run inside the curly braces. For example:

{{ let a = 1 }}

will give us an error. We’ll get the error ‘avoid using JavaScript keyword as the property name: “let”’ in the console.

We also can’t run conditional statements within curly braces:

{{ if (id) { return id } }}

We’ll get the error ‘avoid using JavaScript keyword as the property name: “if”’.

Directives

Directives are special attributes with the v- prefix. We can pass in a single JavaScript expression as the value.

v-for can accept more than one expression separated by ; .

For example, we can conditionally display content with v-if :

<p v-if="show">Hello</p>

If show is truthy, then we’ll see Hello . Otherwise, it won’t be shown.

Arguments

Some directive takes an argument, which is denoted by a colon after the directive name.

For example, we can set the href value of an a element by writing:

<a v-bind:href="'https://medium.com'">Medium </a>

The v-on directive also takes an argument. For example, we can handle the click event of an element by writing:

<a v-on:click="onClick"> Click Me </a>

Then the onClick method in our Vue instance or component will be run when we click on the a element.

Dynamic Arguments

Since Vue.js 2.6.0, we can use a JavaScript expression as an argument for a directive.

For example, we can use it as follows. In src/index.js , we write:

new Vue({  
  el: "#app",  
  data: {  
    click: `${"cl"}${"ick"}`  
  },  
  methods: {  
    onClick() {  
      alert("clicked");  
    }  
  }  
});

Then in index.html , we can use a dynamic argument as follows:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> 
  <body>  
    <div id="app">  
      <a v-on:click="onClick"> Click Me </a>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

This will reference the value of the click property of the data property in our Vue instance, and so we actually have v-on:click .

And therefore, when we click the a tag, we get an alert box with the clicked text displayed.

Dynamic argument expressions must evaluate to a string or null .

Spaces and quotes also aren’t valid inside the brackets.

When Should We Use Watchers?

We can use watchers when we want to watch for data changes in reactive properties and do some asynchronous or expensive options as the value changes.

For example, we can use it to watch for changes for an input and then get a joke from the Chuck Norris API when we type in something.

First, we put the following in src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    message: "",  
    joke: ""  
  },  
  created() {  
    this.debounceGetAllCapsMessage = _.debounce(this.getAllCapsMessage, 500);  
  },  
  methods: {  
    async getAllCapsMessage() {  
      const res = await fetch("https://api.icndb.com/jokes/random");  
      const result = await res.json();  
      this.joke = result.value.joke;  
    }  
  },  
  watch: {  
    message(newMessage, oldMessage) {  
      this.debounceGetAllCapsMessage();  
    }  
  }  
});

The code above watches for the change in the message property and then call the debounceGetAllCapsMessage method, which gets a jokes after a half a second delay.

The watch method both takes the new value as the first parameter and the old value as the second parameter.

Then we put the following in index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>  
  </head> <body>  
    <div id="app">  
      <input type="text" v-model="message" />  
      <p>{{joke}}</p>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

As we can see, we can run an asynchronous operation, add a delay before we perform the operation, and then get the final result.

We can also use vm.$watch to watch for a value. It takes up to 3 arguments.

The first is a path to a property, which is a string. It can also be a function with one or multiple things combined to watch.

The second argument is a function that runs when the value changes.

The third argument for vm.$watch is an object with some properties. They include:

  • immediate — watches for the setting of the initial value

The method returns a function to stop watching for values. This is an optional argument.

We can use it as follows. In src/index.js , we have:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "",  
    joke: ""  
  }  
});

vm.$watch(  
  "message",  
  _.debounce(async function() {  
    const res = await fetch("https://api.icndb.com/jokes/random");  
    const result = await res.json();  
    this.joke = result.value.joke;  
  }, 500)  
);

Then index.html , we have:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>  
  </head> <body>  
    <div id="app">  
      <input type="text" v-model="message" />  
      <p>{{joke}}</p>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

which is the same as what we had before.

In both examples, we should get a new joke after half a second delay displayed.

Using v-for to Map Array to Elements

We can use the v-for directive to render a list of items from an array.

For example, we can use it as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="person in persons">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we have the v-for directive, and then we write person in persons to loop through the data.persons array.

Then we get the name property from each entry and display it.

person is an alias for the array element being iterated on.

Then we get:

JoeJaneMary

All the properties of the array entry is available during iteration.

We can also add an optional second argument to access the index of the array. For example, we can write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]  
  }  
});

index.html ;

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

0 - Joe1 - Jane2 - Mary

We can also use of instead of in to match the for...of loop syntax in JavaScript:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="person of persons">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

v-for with an Object

We can also use v-for to loop through the values of an object.

So if we write the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    book: {  
      title: "JavaScript Book",  
      author: "John Smith",  
      publishedYear: 2019  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="value in book">  
        {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

And we get:

JavaScript BookJohn Smith2019

We can loop through the keys of the object by providing a second argument to the expression as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    book: {  
      title: "JavaScript Book",  
      author: "John Smith",  
      publishedYear: 2019  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="(value, key) in book">  
        {{key}} - {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

title - JavaScript Bookauthor - John SmithpublishedYear - 2019

Also, we can provide a third argument for the index of the object entry.

To do this, we can write:

src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    book: {  
      title: "JavaScript Book",  
      author: "John Smith",  
      publishedYear: 2019  
    }  
  }  
});

index.html :

`<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="(value, key, index) in book">  
        {{index}}: {{key}} - {{value}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

0: title - JavaScript Book1: author - John Smith2: publishedYear - 2019

The order of iteration over an object is based on the enumeration order of Object.keys() , which isn’t guaranteed to be consistent across JavaScript engine implementations.

Maintaining State

v-for by default updates the items in place instead of moving the DOM elements to match the order of the items to make sure it reflects what should be rendered at a particular index.

Therefore, it’s only suitable when the list doesn’t rely on child component state or temporary DOM state like input values.

To make sure Vue knows which elements are unique, we should provide a key attribute via the v-bind:key directive.

For example, we can write:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <div v-for="person in persons" v-bind:key="person.name">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We have to use primitive values as keys.

The key attribute is a generic mechanism for Vue to identify nodes.

v-show

The v-show directive is used to show something when the expression we passed into it is truthy.

For example, we can use it as follows:

<h1 v-show="show">Hi</h1>

The difference between v-show and v-if are that v-show elements remain in the DOM even though is hidden.

On the other hand, v-if elements are hidden by removing them from the DOM.

Also, v-if removes event listeners and child components inside conditional blocks are destroyed if they’re hidden and recreated when v-if ‘s expression is truthy.

v-if elements are also different in that if the condition is falsy during conditional render, it won’t do anything. It won’t be rendered until the expression becomes truthy.

v-if has higher toggling than v-show since DOM manipulating and attaching event listeners have to be done when things are toggled.

Therefore, if things need to be toggled often, v-show is more efficient.

v-if with v-for

v-for has higher priority than v-if if they’re used together.

However, we shouldn’t use them together because of the higher precedence of v-for over v-if .

If we want to filter some elements out when we render items from an array, we should use a computed property.

If we only render a small fraction of array elements, it has to iterate over the entire list and then check if the expression we set it truthy.

For example, the following JavaScript and HTML code:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Jane", "Mary"]  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  <body>  
    <div id="app">  
      <div v-for="person in persons" v-if='person !== "Joe"'>  
        {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

is not very efficient since every time the loop is rendered, Vue has to iterate through every element and then check if person !== “Joe” is true .

Instead, we should use a computed property as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Jane", "Mary"]  
  },  
  computed: {  
    personsWithoutJoe() {  
      return this.persons.filter(p => p !== "Joe");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  <body>  
    <div id="app">  
      <div v-for="person in personsWithoutJoe">  
        {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The code above is more efficient because personsWithoutJoe is only recomputed when persons change.

Also, only the items in personsWithoutJoe is iterated through during render, so v-for doesn’t have to loop through all the items.

There’s also less logic in the template, which keeps it clean. Maintenance is much easier.

We can also get performance benefits from moving v-if to the parent element that has the v-for , so whatever’s inside is only rendered when the condition is met.

For example, if we have:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Jane", "Mary"],  
    show: false  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  <body>  
    <div id="app">  
      <div v-if="show">  
        <div v-for="person in persons">  
          {{person}}  
        </div>  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then nothing is rendered since show is false . Vue doesn’t have to do the work to look at whatever’s inside.

This is much better than:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head>  <body>  
    <div id="app">  
      <div v-for="person in persons" v-if="show">  
        {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, Vue has to loop through each entry and check if the condition in the v-if returns a truthy value.

As we can see, even though the code is only slightly different, but the performance implications are big.

Array Change Detection

Vue wraps the following array mutation methods so that when these methods are called, a view update will be triggered. They’re the following:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

For example, if we have a button that triggers a push of a new item to an array, it’ll update:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }]  
  },  
  methods: {  
    addPerson() {  
      this.persons.push({ name: "Mary" });  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addPerson">Add Person</button>  
      <div v-for="person in persons">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get:

JoeJane

When the array is first rendered. Then when we click Add Button, we get:

JoeJaneMary

since we called the addPerson method with the button click, which called the push method to add a new entry.

Replacing an Array

Non-mutating array method are ones that always return a new array. We can replace the original array with the returned array to trigger a view update.

Vue doesn’t re-render the list from scratch when we update the array. Instead, it’ll try to maximum DOM element reuse.

For example, if we have the following:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: [{ name: "Joe" }, { name: "Jane" }, { name: "Mary" }]  
  },  
  methods: {  
    filterPerson() {  
      this.persons = this.persons.filter(p => p.name !== "Joe");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js)"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="filterPerson">Filter Person</button>  
      <div v-for="person in persons">  
        {{person.name}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when our page first loads, we get the following names:

JoeJaneMary

Once we click Filter Person, we get:

JaneMary

since we called the filterPerson method with the button click, which called the filter method to return an array with the filtered items.

Then the new array is assigned to this.persons and then the view is refreshed.

Catches

Vue can’t detect directly assign an item to an array by setting the index or when the length of the array is modified.

So:

vm.item[indexOfItem] = 'item';

and:

vm.items.length = 2;

won’t be picked up by Vue and update the view with updated data.

We can either call Vue.set or splice to set an entry to a given index.

Vue.set

For example, we can write the following code:

src/index.js

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Mary"]  
  },  
  methods: {  
    addPerson() {  
      Vue.set(this.persons, 5, "Jane");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js)"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addPerson">Add Person</button>  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

When the page first loads, we get the following entries rendered:

0 - Joe1 - Mary

Then when we click Add Person, we get:

0 - Joe1 - Mary2 -3 -4 -5 - Jane

Splice

We can do the same with splice , but the length has to be set first:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Mary"]  
  },  
  methods: {  
    addPerson() {  
      this.persons.length = 5;  
      this.persons.splice(5, 1, "Jane");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addPerson">Add Person</button>  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we get the same results as before.

We can also use splice to set the length of the array and trigger view update. To do this, we can write:

this.persons.splice(5);

vm.$set

We can call vm.$set as follows, which is the same as Vue.set except that it’s available to the Vue instance instead of a global object.

For example, we can write:

src/index.js :

new Vue({  
  el: "#app",  
  data: {  
    persons: ["Joe", "Mary"]  
  },  
  methods: {  
    addPerson() {  
      this.$set(this.persons, 5, "Jane");  
    }  
  }  
});

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <button @click="addPerson">Add Person</button>  
      <div v-for="(person, index) in persons">  
        {{index}} - {{person}}  
      </div>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Transitioning Between Elements

With Vue, we can transition elements between v-if and v-else .

When toggling between elements that have the same tag name, we must tell Vue that they’re distinct elements giving them unique key attributes. Otherwise, the Vue compiler will only replace the content of the element for efficiency.

It’s always a good idea to key multiple items within a transition element.

For example, we can transition between 2 p elements as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: { show: false }  
});

src/style.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 0;  
}

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="show = !show">  
        Toggle  
      </button>  
      <transition name="fade">  
        <p v-if="show" key="hi">hi</p>  
        <p v-else key="bye">bye</p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we should see fade effects when we transition between ‘hi’ and ‘bye’.

We added key attributes to each so they’ll always be rendered from scratch.

Also, we can use the key attribute to transition between different states of the same element.

We can do that as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: { show: false }  
});

src/styles.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 0;  
}

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="show = !show">  
        Toggle  
      </button>  
      <transition name="fade">  
        <p v-bind:key="show">  
          {{ show ? 'hi' : 'bye' }}  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

In the code above, we replaced:

<transition name="fade">  
  <p v-if="show" key="hi">hi</p>  
  <p v-else key="bye">bye</p>  
</transition>

with:

<transition name="fade">  
  <p v-bind:key="show">  
    {{ show ? 'hi' : 'bye' }}  
  </p>  
</transition>

Everything else remained the same.

The transition can be done between any number of elements with v-if or binding a single element to a dynamic property.

For example, we can write:

src/index.js :

new Vue({  
  el: "#app",  
  data: { states: ["foo", "bar", "baz"], state: "", index: 0 },  
  methods: {  
    rotate() {  
      this.index = (this.index + 1) % this.states.length;  
      this.state = this.states[this.index];  
    }  
  }  
});

src/styles.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 0;  
}

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="rotate">  
        Rotate  
      </button>  
      <transition name="fade">  
        <p v-bind:key="state">  
          {{ state }}  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

We can rotate to different state value from the states array. Then we see transition each time the value of state changes.

Also, we can write out the v-if statements for each item as follows:

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="rotate">  
        Rotate  
      </button>  
      <transition name="fade">  
        <p v-if="state === 'foo'" key="foo">  
          foo  
        </p>  
        <p v-if="state === 'bar'" key="bar">  
          bar  
        </p>  
        <p v-if="state === 'baz'" key="baz">  
          baz  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

The rest are unchanged.

Transition Modes

We can set the transition modes to control how old elements are transition to new elements.

There’re 2 transition modes:

  • in-out — new element transitions in first, then the current element transitions out
  • out-in — current element transitions our first and then the new element transitions in

For example, we can use it as follows:

src/index.js :

new Vue({  
  el: "#app",  
  data: { states: ["foo", "bar", "baz"], state: "", index: 0 },  
  methods: {  
    rotate() {  
      this.index = (this.index + 1) % this.states.length;  
      this.state = this.states[this.index];  
    }  
  } 
});

src/styles.css :

.fade-enter-active,  
.fade-leave-active {  
  transition: opacity 0.5s;  
}  
.fade-enter,  
.fade-leave-to {  
  opacity: 0;  
}

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
    <link  
      rel="stylesheet"  
      type="text/css"  
      href="./src/styles.css"  
      media="screen"  
    />  
  </head>  
  <body>  
    <div id="app">  
      <button v-on:click="rotate">  
        Rotate  
      </button>  
      <transition name="fade" mode="out-in">  
        <p v-bind:key="state">  
          {{ state }}  
        </p>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

With the mode attribute added, we no longer have the jumpiness that the previous example has when transitioning between elements since old and new elements won’t exist on the same screen.

Computed Properties

To make more template expressions more concise and reusable, we can create computed properties to compute results from existing properties as their value changes.

We can define computed properties in a Vue instance by putting them in the computed property of the options object that we pass into the Vue constructor.

For example, we can write the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    spelledMessage() {  
      return this.message.split("").join("-");  
    }  
  }  
});

Then we can use it like any other field in our HTML template. So in index.html, we can write:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{message}}</p>  
      <p>{{spelledMessage}}</p>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

We should then get:

foof-o-o

on our screen.

The spelledMessage method is a getter that’s used by Vue.js. It takes the returned value of it and then put it in place of {{spelledMessage}} .

Therefore, the name of the placeholder in the template must be the same as the name of the method that returns the value that we want in the computed object.

We can also get the value from the returned Vue instance as follows:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    spelledMessage() {  
      return this.message.split("").join("-");  
    }  
  }  
});console.log(vm.spelledMessage);

We should see f-o-o logged from the console.log.

We can bind to computed properties in templates like any other property. Vue knows that vm.spelledMessage depends on vm.message, so the bindings will be updated when vm.message is updated.

The computed getter method doesn’t commit side effects, which makes it easy to test and understand.

Computed Caching vs Methods

One good feature of computed properties is that the result of it is cached.

There’s no caching for results returned from methods.

So instead of using methods, we should use computed properties for cases where we’re computing something from existing properties.

A computed property is only re-evaluated once a reactive property is updated.

For example, if we have:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    now() {  
      return Date.now();  
    }  
  }  
});

It’ll never update after its first computed since it doesn’t depend on any reactive property from the data property.

Without caching computed properties keep getting computed as reactive properties that they depend on change, which means the app gets slow as reactive properties that the computed property depends on are changing.

Computed vs Watched Property

Watch properties are useful for watching for changes in a property as the name suggests.

However, because of the caching feature of computed properties, we should use them as much as we can.

For instance, we can simplify the following:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1,  
    person: undefined  
  },  
  watch: {  
    name: {  
      immediate: true,  
      handler(newVal) {  
        this.person = `${newVal} - ${this.age}`;  
      }  
    },  
    age: {  
      immediate: true,  
      handler(newVal) {  
        this.person = `${this.name} - ${newVal}`;  
      }  
    }  
  }  
});

to:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1  
  },  
  computed: {  
    person() {  
      return `${this.name} - ${this.age}`;  
    }  
  }  
});

As we can see, the first example was much more complex. We have to set immediate to true in each property so that it’ll watch the initial value change. Then we have to define the value change handler for each reactive property.

Then in each handler, we have to set the person property so that we can combine name and age.

On the other hand, with computed properties, we only have one method which returns the fields combined in the way we want.

It’s much more convenient for us and the app that we create is also faster because of caching.

Computed Setter

We can also create a setter function for computed properties.

For example, we can write:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1  
  },  
  computed: {  
    person: {  
      get() {  
        return `${this.name} - ${this.age}`;  
      },  
      set(newValue) {  
        const [name, age] = newValue.split("-");  
        this.name = name.trim();  
        this.age = age.trim();  
      }  
    }  
  }  
});

The getter function returns the same thing as before, but now we also have a setter function.

The setter function splits the newValue, which has the computed value from the getter function, we split the result and set the result back to the corresponding reactive properties.

We can use a setter as follows. We can put the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    firstName: "Joe",  
    lastName: "Smith"  
  },  
  computed: {  
    name: {  
      get() {  
        return `${this.firstName} ${this.lastName}`;  
      },  
      set(newValue) {  
        const [firstName, lastName] = newValue.split(" ");  
        this.firstName = (firstName || "").trim();  
        this.lastName = (lastName || "").trim();  
      }  
    }  
  }  
});

And in index.html, we put:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{name}}</p>  
      <input type="text" v-model="name" />  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>

Then when we change the value in the input, we get the new values assigned back to the firstName and lastName fields with the setter.

Conclusion

Vue makes building front end apps easy.

We just have to start by creating a Vue instance.

Then we can add some markup to a template and use JavaScript to create some logic.

In the template, we can render things conditionally and we can render arrays of data.

Then to make them fancier, we can add transitions.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *