javascriptroom guide

Exploring Vue.js: A Practical Hands-On Tutorial

In the ever-evolving landscape of web development, JavaScript frameworks have become indispensable tools for building dynamic, interactive, and scalable applications. Among the most popular options, **Vue.js** stands out for its simplicity, flexibility, and gentle learning curve. Created by Evan You in 2014, Vue.js has gained a massive following due to its "progressive" nature—allowing developers to adopt it incrementally, whether they’re adding interactivity to a simple webpage or building a full-fledged single-page application (SPA). This tutorial is designed to take you from Vue.js novice to confident practitioner. We’ll start with the basics, set up your development environment, explore core concepts through hands-on examples, and even build a practical project to solidify your skills. By the end, you’ll have a strong foundation in Vue.js and be ready to tackle real-world projects.

Table of Contents

  1. What is Vue.js?
  2. Setting Up Your Development Environment
  3. Vue.js Fundamentals
  4. Components: The Building Blocks
  5. State Management with Pinia
  6. Routing with Vue Router
  7. Advanced Concepts: Composition API & Reactivity
  8. Hands-On Project: Build a Todo App
  9. Testing Vue.js Applications
  10. Deployment
  11. Conclusion
  12. References

What is Vue.js?

Vue.js (pronounced “view”) is an open-source JavaScript framework for building user interfaces (UIs) and single-page applications. Its core philosophy revolves around three key principles:

Core Features of Vue.js

  • Reactive Data Binding: Automatically updates the DOM when data changes, eliminating manual DOM manipulation.
  • Component-Based Architecture: Break UIs into reusable, self-contained components for easier maintenance and scalability.
  • Virtual DOM: Improves performance by updating only the necessary parts of the DOM instead of re-rendering the entire page.
  • Flexible Integration: Can be used as a library for small features or as a full framework for large apps (paired with Vue Router and Pinia).
  • Gentle Learning Curve: Easier to pick up compared to React or Angular, making it ideal for beginners and experienced developers alike.

Vue.js vs. Other Frameworks

  • React: Vue’s template syntax is more HTML-like, while React uses JSX. Vue’s reactivity is more implicit, whereas React requires explicit state updates with setState or hooks.
  • Angular: Vue is lighter (20KB minified vs. Angular’s ~143KB) and less opinionated, giving developers more flexibility in project structure.

Setting Up Your Development Environment

To start building with Vue.js, you’ll need a few tools:

1. Install Node.js and npm/yarn

Vue.js projects rely on Node.js and a package manager (npm or yarn).

  • Download Node.js from nodejs.org (LTS version recommended).
  • Verify installation:
    node -v  # Should output v14.x or higher  
    npm -v   # Should output 6.x or higher  
  • (Optional) Install yarn:
    npm install -g yarn  

2. Choose a Build Tool

Vue.js offers two popular build tools:

Option A: Vue CLI (Legacy, but Still Used)

A full-featured CLI for scaffolding projects.

npm install -g @vue/cli  
vue create my-vue-app  # Follow prompts to select features  
cd my-vue-app  
npm run serve  # Starts development server at localhost:8080  

A faster, modern build tool with hot module replacement (HMR).

npm create vite@latest my-vue-app -- --template vue  
cd my-vue-app  
npm install  
npm run dev  # Starts development server at localhost:5173  

3. IDE Setup

For the best experience, use VS Code with these extensions:

  • Volar: Official Vue 3 language support (replaces Vetur).
  • Vue 3 Snippets: Handy code snippets for Vue.

Vue.js Fundamentals

Let’s dive into the basics of Vue.js, starting with the Vue instance and template syntax.

The Vue Instance

Every Vue application starts with a Vue instance. It acts as the root of your app, managing data, methods, and lifecycle hooks.

Example (Options API):

<div id="app">  
  {{ message }}  
</div>  

<script>  
const app = new Vue({  
  el: '#app',  // Mounts the instance to the DOM element with id "app"  
  data() {     // Reactive data properties  
    return {  
      message: 'Hello, Vue!'  
    }  
  }  
})  
</script>  

Result: The page displays Hello, Vue!. If you update app.message = 'Hi, Vue!' in the console, the DOM updates automatically—thanks to Vue’s reactivity!

Template Syntax

Vue uses HTML-based templates with special directives to bind data and add interactivity.

Interpolation

Use double curly braces {{ }} to display reactive data:

<p>{{ message }}</p>  
<p>{{ 1 + 1 }}</p>  <!-- Output: 2 -->  
<p>{{ user.name.toUpperCase() }}</p>  <!-- Output: USER NAME -->  

Directives

Directives are special attributes prefixed with v- that apply reactive behavior to the DOM.

  • v-bind: Binds an attribute to a data property (shorthand: :).

    <img v-bind:src="imageUrl">  
    <a :href="link">Click me</a>  <!-- Shorthand -->  
  • v-on: Listens to DOM events (shorthand: @).

    <button v-on:click="increment">Click me</button>  
    <button @click="decrement">Click me</button>  <!-- Shorthand -->  

    In the Vue instance:

    methods: {  
      increment() { this.count++ },  
      decrement() { this.count-- }  
    }  
  • v-model: Two-way data binding for form inputs.

    <input v-model="username" placeholder="Enter name">  
    <p>Hello, {{ username }}!</p>  <!-- Updates as you type -->  
  • v-if/v-else: Conditionally render elements.

    <p v-if="isLoggedIn">Welcome back!</p>  
    <p v-else>Please log in.</p>  
  • v-for: Render a list.

    <ul>  
      <li v-for="item in items" :key="item.id">  
        {{ item.name }}  
      </li>  
    </ul>  

    :key is required for efficient DOM updates.

Lifecycle Hooks

Vue instances go through a series of lifecycle stages (e.g., created, mounted, updated, destroyed). You can hook into these stages with lifecycle methods:

new Vue({  
  data() { return { message: 'Hello' } },  
  created() {  
    console.log('Instance created! Data is:', this.message);  
  },  
  mounted() {  
    console.log('Instance mounted to DOM!');  
  }  
})  

Components: The Building Blocks

Components are reusable Vue instances that encapsulate HTML, CSS, and JavaScript. They promote code reusability and separation of concerns.

Component Registration

Global Registration

Register a component globally to use it anywhere in your app:

// main.js  
import Vue from 'vue';  
import MyComponent from './components/MyComponent.vue';  

Vue.component('my-component', MyComponent);  // Tag name: <my-component>  

Local Registration

Register a component only in the parent component that needs it:

// ParentComponent.vue  
import ChildComponent from './ChildComponent.vue';  

export default {  
  components: {  
    ChildComponent  // Can be used as <child-component> in the template  
  }  
}  

Props: Passing Data to Child Components

Props allow parent components to pass data to child components.

Child Component (ChildComponent.vue):

export default {  
  props: {  
    name: { type: String, required: true },  // Prop with type and validation  
    age: { type: Number, default: 18 }       // Default value  
  }  
}  

Parent Component Template:

<child-component name="Alice" :age="25"></child-component>  

Emitting Events: Child to Parent

To send data from child to parent, use this.$emit('event-name', data).

Child Component:

<button @click="sendMessage">Send Message</button>  
methods: {  
  sendMessage() {  
    this.$emit('message-sent', 'Hello from child!');  
  }  
}  

Parent Component:

<child-component @message-sent="handleMessage"></child-component>  
methods: {  
  handleMessage(msg) {  
    console.log('Received:', msg);  // Output: "Received: Hello from child!"  
  }  
}  

State Management with Pinia

As your app grows, managing shared state between components becomes complex. Pinia (the successor to Vuex) simplifies this by centralizing state in stores.

Why Pinia?

  • Simpler API than Vuex (no mutations, only actions).
  • Built-in TypeScript support.
  • DevTools integration.
  • Recommended by the Vue team.

Installing Pinia

npm install pinia  

Creating a Store

  1. Initialize Pinia in main.js:

    import { createApp } from 'vue';  
    import { createPinia } from 'pinia';  
    import App from './App.vue';  
    
    const app = createApp(App);  
    app.use(createPinia());  // Make Pinia available to all components  
    app.mount('#app');  
  2. Define a Store (e.g., stores/counter.js):

    import { defineStore } from 'pinia';  
    
    // Define a store with id "counter"  
    export const useCounterStore = defineStore('counter', {  
      state: () => ({ count: 0 }),  // Reactive state  
      getters: {                    // Computed properties  
        doubleCount: (state) => state.count * 2  
      },  
      actions: {                    // Methods to modify state  
        increment() {  
          this.count++;  // "this" refers to the store instance  
        },  
        decrement() {  
          this.count--;  
        }  
      }  
    });  

Using the Store in Components

<script setup>  
import { useCounterStore } from '@/stores/counter';  

const counterStore = useCounterStore();  // Access the store  
</script>  

<template>  
  <p>Count: {{ counterStore.count }}</p>  
  <p>Double Count: {{ counterStore.doubleCount }}</p>  
  <button @click="counterStore.increment">+</button>  
  <button @click="counterStore.decrement">-</button>  
</template>  

Routing with Vue Router

Single-page applications (SPAs) use client-side routing to navigate between views without reloading the page. Vue Router handles this for Vue apps.

Installing Vue Router

npm install vue-router@4  # For Vue 3  

Setting Up Routes

  1. Create Route Components (e.g., views/Home.vue, views/About.vue).

  2. Define Routes in router/index.js:

    import { createRouter, createWebHistory } from 'vue-router';  
    import Home from '../views/Home.vue';  
    import About from '../views/About.vue';  
    
    const routes = [  
      { path: '/', name: 'Home', component: Home },  
      { path: '/about', name: 'About', component: About }  
    ];  
    
    const router = createRouter({  
      history: createWebHistory(),  // Uses HTML5 history mode (no hash in URL)  
      routes  
    });  
    
    export default router;  
  3. Register Router in main.js:

    import router from './router';  
    app.use(router);  

Using Router in Templates

  • router-link: Navigation links (replaces <a> tags).

    <router-link to="/">Home</router-link>  
    <router-link to="/about">About</router-link>  
  • router-view: Where the matched component is rendered.

    <router-view></router-view>  <!-- Displays Home or About based on URL -->  

Dynamic Routes

For dynamic content (e.g., user profiles), use :param:

// Route definition  
{ path: '/user/:id', name: 'User', component: User }  

// In User.vue  
<script setup>  
import { useRoute } from 'vue-router';  

const route = useRoute();  
console.log(route.params.id);  // Access the "id" parameter  
</script>  

Advanced Concepts: Composition API & Reactivity

Vue 3 introduced the Composition API, a more flexible way to organize component logic compared to the Options API.

Composition API vs. Options API

  • Options API: Groups code by options (data, methods, computed). Great for small components.
  • Composition API: Groups code by feature (e.g., all counter logic in one block). Better for large components and code reuse.

Using the Composition API with <script setup>

The <script setup> syntax simplifies Composition API usage:

<script setup>  
import { ref, computed } from 'vue';  

// Reactive state (ref for primitives, reactive for objects)  
const count = ref(0);  

// Computed property  
const doubleCount = computed(() => count.value * 2);  

// Method  
function increment() {  
  count.value++;  // Access ref value with .value  
}  
</script>  

<template>  
  <p>Count: {{ count }}</p>  
  <p>Double: {{ doubleCount }}</p>  
  <button @click="increment">+</button>  
</template>  

Reactivity in Vue 3

Vue 3 uses Proxies to track changes to reactive data. Unlike Vue 2’s Object.defineProperty, Proxies:

  • Work with arrays and dynamic properties.
  • Don’t require Vue.set for adding properties.

Hands-On Project: Build a Todo App

Let’s apply what we’ve learned by building a todo app with:

  • Vite (project setup).
  • Components (TodoInput, TodoList, TodoItem).
  • Pinia (state management).
  • Vue Router (optional: separate views for active/completed todos).

Step 1: Project Setup

npm create vite@latest todo-app -- --template vue  
cd todo-app  
npm install  
npm install pinia vue-router@4  
npm run dev  

Step 2: Create Components

  • components/TodoInput.vue: Input field to add new todos.
  • components/TodoItem.vue: Individual todo item (with delete and toggle).
  • components/TodoList.vue: Lists all todos.

Step 3: Set Up Pinia Store

Create stores/todoStore.js:

import { defineStore } from 'pinia';  

export const useTodoStore = defineStore('todo', {  
  state: () => ({  
    todos: [],  
    nextId: 1  
  }),  
  actions: {  
    addTodo(text) {  
      this.todos.push({ id: this.nextId++, text, completed: false });  
    },  
    toggleTodo(id) {  
      const todo = this.todos.find(t => t.id === id);  
      if (todo) todo.completed = !todo.completed;  
    },  
    deleteTodo(id) {  
      this.todos = this.todos.filter(t => t.id !== id);  
    }  
  }  
});  

Step 4: Assemble the App

In App.vue, use the components and store:

<script setup>  
import TodoInput from './components/TodoInput.vue';  
import TodoList from './components/TodoList.vue';  
</script>  

<template>  
  <div class="app">  
    <h1>Todo App</h1>  
    <TodoInput />  
    <TodoList />  
  </div>  
</template>  

TodoInput.vue:

<script setup>  
import { ref } from 'vue';  
import { useTodoStore } from '@/stores/todoStore';  

const todoText = ref('');  
const todoStore = useTodoStore();  

function addTodo() {  
  if (todoText.value.trim()) {  
    todoStore.addTodo(todoText.value);  
    todoText.value = '';  
  }  
}  
</script>  

<template>  
  <input  
    v-model="todoText"  
    @keyup.enter="addTodo"  
    placeholder="Add a new todo..."  
  >  
  <button @click="addTodo">Add</button>  
</template>  

TodoList.vue:

<script setup>  
import { useTodoStore } from '@/stores/todoStore';  
import TodoItem from './TodoItem.vue';  

const todoStore = useTodoStore();  
</script>  

<template>  
  <div class="todo-list">  
    <TodoItem  
      v-for="todo in todoStore.todos"  
      :key="todo.id"  
      :todo="todo"  
    />  
  </div>  
</template>  

TodoItem.vue:

<script setup>  
import { useTodoStore } from '@/stores/todoStore';  

const props = defineProps({ todo: Object });  
const todoStore = useTodoStore();  
</script>  

<template>  
  <div class="todo-item">  
    <input  
      type="checkbox"  
      :checked="todo.completed"  
      @change="todoStore.toggleTodo(todo.id)"  
    >  
    <span :class="{ completed: todo.completed }">{{ todo.text }}</span>  
    <button @click="todoStore.deleteTodo(todo.id)">×</button>  
  </div>  
</template>  

<style>  
.completed { text-decoration: line-through; color: #888; }  
</style>  

Testing Vue.js Applications

Testing ensures your app works as expected. Vue recommends:

  • Vue Test Utils: Official testing library for Vue components.
  • Jest or Vitest: Test runners.

Example Test for TodoItem.vue (using Vitest):

import { describe, it, expect } from 'vitest';  
import { mount } from '@vue/test-utils';  
import TodoItem from '@/components/TodoItem.vue';  

describe('TodoItem', () => {  
  it('toggles todo when checkbox is clicked', async () => {  
    const todo = { id: 1, text: 'Test todo', completed: false };  
    const wrapper = mount(TodoItem, { props: { todo } });  

    // Click the checkbox  
    await wrapper.find('input[type="checkbox"]').trigger('change');  

    // Assert the todo was toggled (via store action)  
    expect(wrapper.emitted('toggle')).toBeTruthy();  
  });  
});  

Deployment

Once your app is ready, deploy it to a hosting service. Here’s how to deploy to Netlify:

  1. Build the Project:

    npm run build  # Generates a "dist" folder with optimized assets  
  2. Push to GitHub:
    Create a repo and push your code.

  3. Deploy on Netlify:

    • Go to netlify.com and connect your GitHub repo.
    • Set build command: npm run build
    • Set publish directory: dist
    • Click “Deploy site”.

Conclusion

Vue.js is a powerful yet approachable framework for building modern web apps. In this tutorial, we covered:

  • Vue.js basics (instance, templates, directives).
  • Components, props, and events.
  • State management with Pinia.
  • Routing with Vue Router.
  • Advanced concepts like the Composition API.
  • Building and deploying a practical todo app.

To deepen your skills, explore the Vue ecosystem:

  • Nuxt.js: Server-side rendering (SSR) and static site generation (SSG) for Vue.
  • VueUse: Collection of utility composables.
  • Vuetify or Element Plus: UI component libraries.

References