Om Pandey
Vue.js Framework

Vue.js Framework

The Progressive JavaScript Framework

Master Vue.js - the approachable, performant, and versatile framework for building modern web interfaces. From simple widgets to full-featured SPAs!

Lightweight Reactive Component-Based Flexible

What You'll Learn

1

Introduction to Vue.js

Vue.js is a progressive JavaScript framework for building user interfaces. Unlike monolithic frameworks, Vue is designed to be incrementally adoptable. The core library focuses on the view layer only, making it easy to integrate with other libraries or existing projects.

Why Vue.js?

Easy to Learn: Vue has a gentle learning curve with excellent documentation. Used by Alibaba, Xiaomi, GitLab, and Nintendo! Perfect for both beginners and experts.

Vue.js Component Architecture
Template (HTML)
Script (Logic)
Style (CSS)
Component
Key Features:
  • Reactive Data Binding: Automatic UI updates when data changes
  • Component System: Reusable, self-contained UI pieces
  • Virtual DOM: Efficient rendering for better performance
  • Single-File Components: Template, script, and style in one .vue file
Feature Vue 2 Vue 3
API Style Options API Options + Composition API
Reactivity Object.defineProperty Proxy-based
Performance Good Faster & Smaller bundle
TypeScript Limited support Full support
State Management Vuex Pinia (recommended)
2

Installation & Setup

Method 1: Using create-vue (Recommended)

Terminal
$ # Create a new Vue project $ npm create vue@latest ✔ Project name: my-vue-app ✔ Add TypeScript? No / Yes ✔ Add JSX Support? No / Yes ✔ Add Vue Router? No / Yes ✔ Add Pinia for state management? No / Yes ✔ Add Vitest for Unit Testing? No / Yes ✔ Add ESLint for code quality? No / Yes $ # Navigate to project $ cd my-vue-app $ # Install dependencies $ npm install $ # Start development server $ npm run dev VITE v5.0.0 ready in 300 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose

Method 2: Using CDN (Quick Start)

index.html
<!DOCTYPE html>
<html>
<head>
    <title>My Vue App</title>
    <!-- Import Vue 3 from CDN -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
    <div id="app">
        {{ message }}
    </div>

    <script>
        const { createApp } = Vue

        createApp({
            data() {
                return {
                    message: 'Hello Vue!'
                }
            }
        }).mount('#app')
    </script>
</body>
</html>

Project Structure

📁 my-vue-app/ ├── 📁 node_modules/ ├── 📁 public/ │ └── favicon.ico ├── 📁 src/ │ ├── 📁 assets/ # Static assets (images, fonts) │ ├── 📁 components/ # Reusable components │ │ └── HelloWorld.vue │ ├── 📁 views/ # Page components │ ├── 📁 router/ # Vue Router config │ ├── 📁 stores/ # Pinia stores │ ├── App.vue # Root component │ └── main.js # Entry point ├── index.html ├── package.json └── vite.config.js # Vite configuration

Vue Project Created!

Open http://localhost:5173 to see your Vue app running!

3

Vue Basics & Instance

Creating a Vue Application

src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'

// Create Vue application
const app = createApp(App)

// Use plugins
app.use(router)
app.use(createPinia())

// Global configuration
app.config.errorHandler = (err) => {
    console.error('Global error:', err)
}

// Mount to DOM
app.mount('#app')

Single-File Component (SFC)

src/App.vue
Vue
<!-- Template - HTML structure -->
<template>
    <div class="app">
        <h1>{{ title }}</h1>
        <p>Count: {{ count }}</p>
        <button @click="increment">Increment</button>
    </div>
</template>

<!-- Script - JavaScript logic -->
<script>
export default {
    // Component name
    name: 'App',
    
    // Reactive data
    data() {
        return {
            title: 'Hello Vue!',
            count: 0
        }
    },
    
    // Methods
    methods: {
        increment() {
            this.count++
        }
    }
}
</script>

<!-- Style - CSS styling -->
<style scoped>
.app {
    text-align: center;
    padding: 20px;
}

button {
    padding: 10px 20px;
    font-size: 16px;
    cursor: pointer;
}
</style>

Scoped Styles

The scoped attribute ensures styles only apply to the current component, preventing CSS conflicts with other components.

4

Template Syntax

Vue uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying component's data.

Text Interpolation

Template.vue
Vue
<template>
    <!-- Basic interpolation -->
    <p>Message: {{ message }}</p>
    
    <!-- JavaScript expressions -->
    <p>{{ message.split('').reverse().join('') }}</p>
    <p>{{ count + 1 }}</p>
    <p>{{ isActive ? 'Yes' : 'No' }}</p>
    
    <!-- Raw HTML (use with caution!) -->
    <p v-html="rawHtml"></p>
    
    <!-- One-time interpolation -->
    <p v-once>This will never change: {{ message }}</p>
</template>

<script>
export default {
    data() {
        return {
            message: 'Hello Vue!',
            count: 5,
            isActive: true,
            rawHtml: '<strong>Bold text</strong>'
        }
    }
}
</script>
Browser Output
Message: Hello Vue! !euV olleH 6 Yes Bold text This will never change: Hello Vue!

Attribute Binding

AttributeBinding.vue
Vue
<template>
    <!-- v-bind shorthand : -->
    <img :src="imageUrl" :alt="imageAlt">
    
    <!-- Dynamic class -->
    <div :class="{ active: isActive, error: hasError }"></div>
    <div :class="[activeClass, errorClass]"></div>
    
    <!-- Dynamic style -->
    <div :style="{ color: textColor, fontSize: fontSize + 'px' }">
        Styled text
    </div>
    
    <!-- Multiple attributes -->
    <button v-bind="buttonAttrs">Click me</button>
    
    <!-- Boolean attributes -->
    <button :disabled="isDisabled">Submit</button>
</template>

<script>
export default {
    data() {
        return {
            imageUrl: '/logo.png',
            imageAlt: 'Logo',
            isActive: true,
            hasError: false,
            activeClass: 'active',
            errorClass: '',
            textColor: '#42b883',
            fontSize: 18,
            isDisabled: false,
            buttonAttrs: {
                id: 'submit-btn',
                type: 'submit',
                class: 'btn btn-primary'
            }
        }
    }
}
</script>
5

Directives

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

Conditional Rendering (v-if, v-show)

Conditional.vue
Vue
<template>
    <!-- v-if: Adds/removes from DOM -->
    <div v-if="type === 'A'">Type A</div>
    <div v-else-if="type === 'B'">Type B</div>
    <div v-else>Type C</div>
    
    <!-- v-show: Toggles display CSS -->
    <p v-show="isVisible">I am visible!</p>
    
    <!-- v-if on template (no wrapper element) -->
    <template v-if="showGroup">
        <h2>Title</h2>
        <p>Content</p>
    </template>
    
    <button @click="toggle">Toggle</button>
</template>

<script>
export default {
    data() {
        return {
            type: 'A',
            isVisible: true,
            showGroup: true
        }
    },
    methods: {
        toggle() {
            this.isVisible = !this.isVisible
        }
    }
}
</script>

v-if vs v-show

v-if: True conditional rendering - elements are destroyed/created.
v-show: Always rendered, just toggles CSS display property.
Use v-show for frequent toggles, v-if for rare changes.

List Rendering (v-for)

ListRendering.vue
Vue
<template>
    <!-- Loop through array -->
    <ul>
        <li v-for="item in items" :key="item.id">
            {{ item.name }}
        </li>
    </ul>
    
    <!-- With index -->
    <ul>
        <li v-for="(item, index) in items" :key="item.id">
            {{ index + 1 }}. {{ item.name }}
        </li>
    </ul>
    
    <!-- Loop through object -->
    <div v-for="(value, key) in user" :key="key">
        {{ key }}: {{ value }}
    </div>
    
    <!-- Range -->
    <span v-for="n in 10" :key="n">{{ n }} </span>
    
    <!-- v-for with v-if (use template) -->
    <template v-for="item in items" :key="item.id">
        <li v-if="item.isActive">{{ item.name }}</li>
    </template>
</template>

<script>
export default {
    data() {
        return {
            items: [
                { id: 1, name: 'Vue', isActive: true },
                { id: 2, name: 'React', isActive: true },
                { id: 3, name: 'Angular', isActive: false }
            ],
            user: {
                name: 'Om Pandey',
                role: 'Developer',
                location: 'Nepal'
            }
        }
    }
}
</script>

Always Use :key

The :key attribute is essential for Vue to track each element's identity. Always use unique identifiers, not array indices, for optimal performance.

Event Handling (v-on / @)

EventHandling.vue
Vue
<template>
    <!-- Click event -->
    <button @click="handleClick">Click me</button>
    
    <!-- Inline expression -->
    <button @click="count++">Count: {{ count }}</button>
    
    <!-- Pass arguments -->
    <button @click="greet('Hello', $event)">Greet</button>
    
    <!-- Event modifiers -->
    <form @submit.prevent="onSubmit">
        <input @keyup.enter="search">
        <button type="submit">Submit</button>
    </form>
    
    <!-- Mouse modifiers -->
    <div @click.right="onRightClick">Right-click me</div>
    
    <!-- Once modifier -->
    <button @click.once="doOnce">Only once</button>
    
    <!-- Stop propagation -->
    <div @click="outer">
        <button @click.stop="inner">Click</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            count: 0
        }
    },
    methods: {
        handleClick() {
            console.log('Clicked!')
        },
        greet(message, event) {
            console.log(message, event.target)
        },
        onSubmit() {
            console.log('Form submitted!')
        },
        search() {
            console.log('Searching...')
        }
    }
}
</script>

Two-Way Binding (v-model)

FormBinding.vue
Vue
<template>
    <!-- Text input -->
    <input v-model="message" placeholder="Enter message">
    <p>Message: {{ message }}</p>
    
    <!-- Textarea -->
    <textarea v-model="bio"></textarea>
    
    <!-- Checkbox -->
    <input type="checkbox" v-model="checked">
    <label>{{ checked ? 'Checked' : 'Not checked' }}</label>
    
    <!-- Multiple checkboxes -->
    <input type="checkbox" v-model="hobbies" value="coding"> Coding
    <input type="checkbox" v-model="hobbies" value="gaming"> Gaming
    <input type="checkbox" v-model="hobbies" value="music"> Music
    <p>Hobbies: {{ hobbies }}</p>
    
    <!-- Radio buttons -->
    <input type="radio" v-model="gender" value="male"> Male
    <input type="radio" v-model="gender" value="female"> Female
    
    <!-- Select dropdown -->
    <select v-model="selected">
        <option disabled value="">Select one</option>
        <option>Vue</option>
        <option>React</option>
        <option>Angular</option>
    </select>
    
    <!-- Modifiers -->
    <input v-model.lazy="lazy">      <!-- Sync on change -->
    <input v-model.number="age">     <!-- Cast to number -->
    <input v-model.trim="text">      <!-- Trim whitespace -->
</template>

<script>
export default {
    data() {
        return {
            message: '',
            bio: '',
            checked: false,
            hobbies: [],
            gender: '',
            selected: '',
            age: 0
        }
    }
}
</script>
6

Reactivity & Data Binding

Vue's reactivity system automatically tracks JavaScript state changes and efficiently updates the DOM when changes occur.

Vue Reactivity Flow
Data Changes
Proxy Detection
Re-render
DOM Update
Reactivity.vue
Vue
<template>
    <div>
        <p>Count: {{ count }}</p>
        <p>User: {{ user.name }} - {{ user.email }}</p>
        <ul>
            <li v-for="item in items" :key="item">{{ item }}</li>
        </ul>
        
        <button @click="updateData">Update Data</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            count: 0,
            user: {
                name: 'Om',
                email: '[email protected]'
            },
            items: ['Vue', 'React']
        }
    },
    methods: {
        updateData() {
            // All these trigger reactive updates
            this.count++
            
            // Update object property
            this.user.name = 'Om Pandey'
            
            // Add new property (Vue 3 handles this automatically)
            this.user.age = 25
            
            // Array mutations
            this.items.push('Angular')
            
            // Replace array
            this.items = [...this.items, 'Svelte']
        }
    }
}
</script>
7

Computed Properties & Watchers

Computed Properties

Computed properties are cached based on their reactive dependencies and only re-evaluate when dependencies change.

Computed.vue
Vue
<template>
    <div>
        <input v-model="firstName" placeholder="First name">
        <input v-model="lastName" placeholder="Last name">
        
        <p>Full Name: {{ fullName }}</p>
        <p>Reversed: {{ reversedFullName }}</p>
        
        <!-- Writable computed -->
        <input v-model="fullName">
        
        <!-- Filtering example -->
        <input v-model="searchQuery" placeholder="Search...">
        <ul>
            <li v-for="item in filteredItems" :key="item.id">
                {{ item.name }}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            firstName: 'Om',
            lastName: 'Pandey',
            searchQuery: '',
            items: [
                { id: 1, name: 'Vue.js' },
                { id: 2, name: 'React' },
                { id: 3, name: 'Angular' },
                { id: 4, name: 'Svelte' }
            ]
        }
    },
    computed: {
        // Basic computed property
        fullName: {
            // Getter
            get() {
                return this.firstName + ' ' + this.lastName
            },
            // Setter
            set(newValue) {
                const [first, last] = newValue.split(' ')
                this.firstName = first
                this.lastName = last || ''
            }
        },
        
        reversedFullName() {
            return this.fullName.split('').reverse().join('')
        },
        
        // Filtered list
        filteredItems() {
            return this.items.filter(item => 
                item.name.toLowerCase().includes(this.searchQuery.toLowerCase())
            )
        }
    }
}
</script>

Watchers

Watchers allow you to perform side effects when reactive data changes.

Watchers.vue
Vue
<template>
    <div>
        <input v-model="searchQuery" placeholder="Search...">
        <p v-if="loading">Loading...</p>
        <p>Results: {{ results }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            searchQuery: '',
            loading: false,
            results: null,
            user: {
                name: 'Om',
                profile: {
                    email: '[email protected]'
                }
            }
        }
    },
    watch: {
        // Basic watcher
        searchQuery(newValue, oldValue) {
            console.log(`Query changed from ${oldValue} to ${newValue}`)
            this.fetchResults(newValue)
        },
        
        // Immediate watcher (runs on mount)
        searchQuery: {
            handler(newValue) {
                this.fetchResults(newValue)
            },
            immediate: true
        },
        
        // Deep watcher (watch nested properties)
        user: {
            handler(newValue) {
                console.log('User changed:', newValue)
            },
            deep: true
        },
        
        // Watch specific nested property
        'user.profile.email'(newEmail) {
            console.log('Email changed to:', newEmail)
        }
    },
    methods: {
        async fetchResults(query) {
            if (!query) return
            
            this.loading = true
            // Simulate API call
            await new Promise(r => setTimeout(r, 500))
            this.results = `Results for: ${query}`
            this.loading = false
        }
    }
}
</script>
8

Components

Components are reusable Vue instances with their own template, logic, and styling. They form the building blocks of Vue applications.

Creating Components

components/Button.vue
Vue
<template>
    <button 
        :class="['btn', `btn-${variant}`, { 'btn-disabled': disabled }]"
        :disabled="disabled"
        @click="handleClick"
    >
        <slot>Button</slot>
    </button>
</template>

<script>
export default {
    name: 'BaseButton',
    
    props: {
        variant: {
            type: String,
            default: 'primary',
            validator: (value) => ['primary', 'secondary', 'danger'].includes(value)
        },
        disabled: {
            type: Boolean,
            default: false
        }
    },
    
    emits: ['click'],
    
    methods: {
        handleClick(event) {
            if (!this.disabled) {
                this.$emit('click', event)
            }
        }
    }
}
</script>

<style scoped>
.btn {
    padding: 10px 20px;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-weight: 600;
    transition: all 0.2s;
}

.btn-primary {
    background: #42b883;
    color: white;
}

.btn-secondary {
    background: #35495e;
    color: white;
}

.btn-danger {
    background: #ef4444;
    color: white;
}

.btn-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
</style>

Using Components

views/Home.vue
Vue
<template>
    <div class="home">
        <h1>Welcome</h1>
        
        <!-- Using the Button component -->
        <BaseButton @click="handleClick">
            Click Me
        </BaseButton>
        
        <BaseButton variant="secondary">
            Secondary
        </BaseButton>
        
        <BaseButton variant="danger" disabled>
            Disabled
        </BaseButton>
        
        <!-- Using Card component -->
        <Card 
            v-for="item in items" 
            :key="item.id"
            :title="item.title"
            :description="item.description"
        />
    </div>
</template>

<script>
import BaseButton from '@/components/Button.vue'
import Card from '@/components/Card.vue'

export default {
    name: 'HomeView',
    
    components: {
        BaseButton,
        Card
    },
    
    data() {
        return {
            items: [
                { id: 1, title: 'Vue.js', description: 'Progressive Framework' },
                { id: 2, title: 'Vite', description: 'Next Gen Build Tool' }
            ]
        }
    },
    
    methods: {
        handleClick() {
            console.log('Button clicked!')
        }
    }
}
</script>
9

Props & Events

Component Communication
Parent
→ Props →
Child
→ Events →
Parent

Props (Parent → Child)

components/UserCard.vue
Vue
<template>
    <div class="user-card">
        <img :src="avatar" :alt="name">
        <h3>{{ name }}</h3>
        <p>{{ email }}</p>
        <span :class="['badge', isActive ? 'active' : 'inactive']">
            {{ isActive ? 'Online' : 'Offline' }}
        </span>
    </div>
</template>

<script>
export default {
    name: 'UserCard',
    
    props: {
        // Simple prop
        name: String,
        
        // Required prop with type
        email: {
            type: String,
            required: true
        },
        
        // Prop with default value
        avatar: {
            type: String,
            default: '/default-avatar.png'
        },
        
        // Boolean prop
        isActive: {
            type: Boolean,
            default: false
        },
        
        // Array/Object default (must be factory function)
        skills: {
            type: Array,
            default: () => []
        },
        
        // Custom validator
        role: {
            type: String,
            validator: (value) => {
                return ['admin', 'user', 'guest'].includes(value)
            }
        }
    }
}
</script>

Events (Child → Parent)

components/Counter.vue (Child)
Vue
<template>
    <div class="counter">
        <button @click="decrement">-</button>
        <span>{{ count }}</span>
        <button @click="increment">+</button>
    </div>
</template>

<script>
export default {
    name: 'Counter',
    
    props: {
        count: {
            type: Number,
            required: true
        }
    },
    
    // Declare emitted events
    emits: ['update:count', 'change'],
    
    methods: {
        increment() {
            this.$emit('update:count', this.count + 1)
            this.$emit('change', { action: 'increment', value: this.count + 1 })
        },
        decrement() {
            this.$emit('update:count', this.count - 1)
            this.$emit('change', { action: 'decrement', value: this.count - 1 })
        }
    }
}
</script>
Parent.vue
Vue
<template>
    <div>
        <!-- v-model for two-way binding -->
        <Counter v-model:count="myCount" @change="handleChange" />
        
        <p>Parent Count: {{ myCount }}</p>
    </div>
</template>

<script>
import Counter from '@/components/Counter.vue'

export default {
    components: { Counter },
    
    data() {
        return {
            myCount: 0
        }
    },
    
    methods: {
        handleChange(payload) {
            console.log('Counter changed:', payload)
        }
    }
}
</script>
10

Slots

Slots allow parent components to pass template content into child components.

components/Card.vue
Vue
<template>
    <div class="card">
        <!-- Named slot: header -->
        <header class="card-header">
            <slot name="header">Default Header</slot>
        </header>
        
        <!-- Default slot -->
        <main class="card-body">
            <slot>Default content</slot>
        </main>
        
        <!-- Named slot: footer -->
        <footer class="card-footer">
            <slot name="footer"></slot>
        </footer>
    </div>
</template>
Using Card with Slots
Vue
<template>
    <Card>
        <!-- Header slot -->
        <template #header>
            <h2>My Custom Header</h2>
        </template>
        
        <!-- Default slot content -->
        <p>This goes into the default slot!</p>
        <p>Multiple elements are allowed.</p>
        
        <!-- Footer slot -->
        <template #footer>
            <button>Save</button>
            <button>Cancel</button>
        </template>
    </Card>
</template>

Scoped Slots

components/List.vue
Vue
<template>
    <ul>
        <li v-for="item in items" :key="item.id">
            <!-- Pass data to parent via slot props -->
            <slot :item="item" :index="index">
                {{ item.name }}
            </slot>
        </li>
    </ul>
</template>

<!-- Usage -->
<List :items="users">
    <template #default="{ item, index }">
        <span>{{ index + 1 }}. {{ item.name }} - {{ item.email }}</span>
    </template>
</List>
11

Composition API

The Composition API provides a set of additive, function-based APIs that allow flexible composition of component logic.

CompositionAPI.vue
Vue
<script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'

// Reactive references
const count = ref(0)
const message = ref('Hello')

// Reactive object
const user = reactive({
    name: 'Om',
    email: '[email protected]'
})

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

const fullName = computed({
    get() {
        return user.name
    },
    set(newValue) {
        user.name = newValue
    }
})

// Methods
function increment() {
    count.value++
}

const decrement = () => {
    count.value--
}

// Watchers
watch(count, (newValue, oldValue) => {
    console.log(`Count: ${oldValue}${newValue}`)
})

watch(
    () => user.name,
    (newName) => {
        console.log('Name changed:', newName)
    }
)

// Lifecycle hooks
onMounted(() => {
    console.log('Component mounted!')
})
</script>

<template>
    <div>
        <p>Count: {{ count }}</p>
        <p>Double: {{ doubleCount }}</p>
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
        
        <input v-model="user.name">
        <p>{{ user.name }} - {{ user.email }}</p>
    </div>
</template>

Composables (Reusable Logic)

composables/useFetch.js
JavaScript
import { ref } from 'vue'

export function useFetch(url) {
    const data = ref(null)
    const error = ref(null)
    const loading = ref(false)

    const fetchData = async () => {
        loading.value = true
        error.value = null
        
        try {
            const response = await fetch(url)
            data.value = await response.json()
        } catch (err) {
            error.value = err.message
        } finally {
            loading.value = false
        }
    }

    return { data, error, loading, fetchData }
}

// Usage in component
// <script setup>
// import { useFetch } from '@/composables/useFetch'
// const { data, loading, error, fetchData } = useFetch('/api/users')
// onMounted(fetchData)
// </script>
12

Vue Router

Terminal
$ # Install Vue Router $ npm install vue-router@4

Router Configuration

src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

// Import views
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'

// Define routes
const routes = [
    {
        path: '/',
        name: 'Home',
        component: Home
    },
    {
        path: '/about',
        name: 'About',
        component: About
    },
    // Dynamic route
    {
        path: '/user/:id',
        name: 'User',
        component: () => import('@/views/User.vue'),
        props: true
    },
    // Nested routes
    {
        path: '/dashboard',
        component: () => import('@/views/Dashboard.vue'),
        children: [
            {
                path: '',
                name: 'DashboardHome',
                component: () => import('@/views/DashboardHome.vue')
            },
            {
                path: 'settings',
                name: 'Settings',
                component: () => import('@/views/Settings.vue')
            }
        ]
    },
    // Protected route
    {
        path: '/admin',
        name: 'Admin',
        component: () => import('@/views/Admin.vue'),
        meta: { requiresAuth: true }
    },
    // 404 catch-all
    {
        path: '/:pathMatch(.*)*',
        name: 'NotFound',
        component: () => import('@/views/NotFound.vue')
    }
]

// Create router instance
const router = createRouter({
    history: createWebHistory(),
    routes,
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
            return savedPosition
        }
        return { top: 0 }
    }
})

// Navigation guard
router.beforeEach((to, from, next) => {
    const isAuthenticated = localStorage.getItem('token')
    
    if (to.meta.requiresAuth && !isAuthenticated) {
        next({ name: 'Home' })
    } else {
        next()
    }
})

export default router

Using Router in Components

App.vue
Vue
<template>
    <div id="app">
        <nav>
            <!-- Router links -->
            <RouterLink to="/">Home</RouterLink>
            <RouterLink :to="{ name: 'About' }">About</RouterLink>
            <RouterLink :to="{ name: 'User', params: { id: 1 } }">User 1</RouterLink>
        </nav>
        
        <!-- Route content renders here -->
        <RouterView />
    </div>
</template>
views/User.vue
Vue
<script setup>
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// Access route params
const userId = route.params.id
const query = route.query

// Programmatic navigation
function goToHome() {
    router.push('/')
}

function goToUser(id) {
    router.push({ name: 'User', params: { id } })
}

function goBack() {
    router.back()
}

function replaceRoute() {
    router.replace('/about')
}
</script>

<template>
    <div>
        <h1>User {{ userId }}</h1>
        <button @click="goToHome">Go Home</button>
        <button @click="goBack">Go Back</button>
    </div>
</template>
13

State Management (Pinia)

Pinia is the official state management library for Vue 3, offering a simpler and more intuitive API than Vuex.

Terminal
$ # Install Pinia $ npm install pinia

Creating a Store

src/stores/counter.js
import { defineStore } from 'pinia'

// Option Store (similar to Options API)
export const useCounterStore = defineStore('counter', {
    // State
    state: () => ({
        count: 0,
        name: 'Counter'
    }),
    
    // Getters (computed)
    getters: {
        doubleCount: (state) => state.count * 2,
        doubleCountPlusOne() {
            return this.doubleCount + 1
        }
    },
    
    // Actions (methods)
    actions: {
        increment() {
            this.count++
        },
        decrement() {
            this.count--
        },
        async incrementAsync() {
            await new Promise(r => setTimeout(r, 1000))
            this.count++
        }
    }
})
src/stores/user.js (Setup Store)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

// Setup Store (Composition API style)
export const useUserStore = defineStore('user', () => {
    // State
    const user = ref(null)
    const isLoading = ref(false)
    
    // Getters
    const isLoggedIn = computed(() => !!user.value)
    const fullName = computed(() => 
        user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
    )
    
    // Actions
    async function login(credentials) {
        isLoading.value = true
        try {
            const response = await fetch('/api/login', {
                method: 'POST',
                body: JSON.stringify(credentials)
            })
            user.value = await response.json()
        } finally {
            isLoading.value = false
        }
    }
    
    function logout() {
        user.value = null
    }
    
    return {
        user,
        isLoading,
        isLoggedIn,
        fullName,
        login,
        logout
    }
})

Using Stores in Components

Counter.vue
Vue
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
import { useUserStore } from '@/stores/user'

const counterStore = useCounterStore()
const userStore = useUserStore()

// Destructure with storeToRefs to keep reactivity
const { count, doubleCount } = storeToRefs(counterStore)
const { increment, decrement } = counterStore

const { user, isLoggedIn } = storeToRefs(userStore)
</script>

<template>
    <div>
        <h2>Count: {{ count }}</h2>
        <p>Double: {{ doubleCount }}</p>
        
        <button @click="increment">+</button>
        <button @click="decrement">-</button>
        <button @click="counterStore.incrementAsync()">Async +</button>
        
        <!-- Reset state -->
        <button @click="counterStore.$reset()">Reset</button>
        
        <!-- Patch state -->
        <button @click="counterStore.$patch({ count: 100 })">Set 100</button>
        
        <div v-if="isLoggedIn">
            Welcome, {{ user.name }}!
        </div>
    </div>
</template>
14

Lifecycle Hooks

Vue Component Lifecycle
beforeCreate
created
beforeMount
mounted
Lifecycle.vue (Composition API)
Vue
<script setup>
import { 
    onBeforeMount,
    onMounted,
    onBeforeUpdate,
    onUpdated,
    onBeforeUnmount,
    onUnmounted,
    onActivated,
    onDeactivated,
    onErrorCaptured
} from 'vue'

// Before component is mounted to DOM
onBeforeMount(() => {
    console.log('Before mount - DOM not ready')
})

// Component is mounted - DOM is ready
onMounted(() => {
    console.log('Mounted - DOM is ready!')
    // Fetch data, setup listeners, access DOM
})

// Before reactive data update
onBeforeUpdate(() => {
    console.log('Before update')
})

// After reactive data update
onUpdated(() => {
    console.log('Updated')
})

// Before component is destroyed
onBeforeUnmount(() => {
    console.log('Before unmount - cleanup!')
    // Remove event listeners, cancel timers
})

// Component is destroyed
onUnmounted(() => {
    console.log('Unmounted')
})

// For keep-alive components
onActivated(() => {
    console.log('Component activated')
})

onDeactivated(() => {
    console.log('Component deactivated')
})

// Error handling
onErrorCaptured((error, instance, info) => {
    console.error('Error captured:', error)
    return false // Prevent error propagation
})
</script>
Lifecycle.vue (Options API)
Vue
<script>
export default {
    beforeCreate() {
        console.log('beforeCreate')
    },
    created() {
        console.log('created - data is reactive')
    },
    beforeMount() {
        console.log('beforeMount')
    },
    mounted() {
        console.log('mounted - DOM ready')
    },
    beforeUpdate() {
        console.log('beforeUpdate')
    },
    updated() {
        console.log('updated')
    },
    beforeUnmount() {
        console.log('beforeUnmount')
    },
    unmounted() {
        console.log('unmounted')
    }
}
</script>
15

API Integration

Fetching Data with Axios

Terminal
$ npm install axios
src/api/axios.js
import axios from 'axios'

// Create axios instance
const api = axios.create({
    baseURL: 'https://api.example.com',
    timeout: 10000,
    headers: {
        'Content-Type': 'application/json'
    }
})

// Request interceptor
api.interceptors.request.use(
    (config) => {
        const token = localStorage.getItem('token')
        if (token) {
            config.headers.Authorization = `Bearer ${token}`
        }
        return config
    },
    (error) => Promise.reject(error)
)

// Response interceptor
api.interceptors.response.use(
    (response) => response,
    (error) => {
        if (error.response?.status === 401) {
            // Handle unauthorized
            localStorage.removeItem('token')
            window.location.href = '/login'
        }
        return Promise.reject(error)
    }
)

export default api

API Service & Component Usage

src/api/userService.js
import api from './axios'

export const userService = {
    getAll() {
        return api.get('/users')
    },
    
    getById(id) {
        return api.get(`/users/${id}`)
    },
    
    create(data) {
        return api.post('/users', data)
    },
    
    update(id, data) {
        return api.put(`/users/${id}`, data)
    },
    
    delete(id) {
        return api.delete(`/users/${id}`)
    }
}
views/Users.vue
Vue
<script setup>
import { ref, onMounted } from 'vue'
import { userService } from '@/api/userService'

const users = ref([])
const loading = ref(false)
const error = ref(null)

async function fetchUsers() {
    loading.value = true
    error.value = null
    
    try {
        const response = await userService.getAll()
        users.value = response.data
    } catch (err) {
        error.value = err.message
    } finally {
        loading.value = false
    }
}

async function deleteUser(id) {
    if (!confirm('Are you sure?')) return
    
    try {
        await userService.delete(id)
        users.value = users.value.filter(u => u.id !== id)
    } catch (err) {
        alert('Failed to delete user')
    }
}

onMounted(fetchUsers)
</script>

<template>
    <div class="users">
        <h1>Users</h1>
        
        <!-- Loading state -->
        <div v-if="loading" class="loading">
            <i data-lucide="loader-2" class="spin"></i>
            Loading...
        </div>
        
        <!-- Error state -->
        <div v-else-if="error" class="error">
            <i data-lucide="alert-circle"></i>
            {{ error }}
            <button @click="fetchUsers">Retry</button>
        </div>
        
        <!-- Data -->
        <div v-else>
            <div v-for="user in users" :key="user.id" class="user-card">
                <h3>{{ user.name }}</h3>
                <p>{{ user.email }}</p>
                <button @click="deleteUser(user.id)">
                    <i data-lucide="trash-2"></i>
                </button>
            </div>
            
            <p v-if="users.length === 0">No users found.</p>
        </div>
    </div>
</template>
Browser Output
Users 1. Om Pandey - [email protected] [Delete] 2. John Doe - [email protected] [Delete] 3. Jane Smith - [email protected] [Delete]

Pro Tips

Error Handling: Always handle loading and error states.
Composables: Extract API logic into reusable composables.
Caching: Use libraries like TanStack Query for advanced caching.

Congratulations!

You've completed the Vue.js guide! You're now equipped to build amazing frontend applications. Keep practicing and building projects!