Vue JS Master Class


The Complete Vue.js Architecture Guide

A comprehensive, expert-level technical manual covering everything from basic HTML integration to advanced Composition API patterns, Pinia, routing, and performance optimization.

1. Setup & Core Basics

How to make an HTML page a Vue JS App

To use Vue without a build step, you can include it via a CDN. You mount a Vue instance to a DOM element (usually a <div id="app">).

<!-- 1. Include Vue from CDN -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

<!-- 2. Create a mount point -->
<div id="app">
  {{ message }}
</div>

<!-- 3. Initialize App -->
<script>
  const { createApp, ref } = Vue;
  createApp({
    setup() {
      const message = ref('Hello Vue!');
      return { message };
    }
  }).mount('#app');
</script>

Project Setup with Vite (Modern Approach)

For professional development, always use Vite or Vue CLI. Vite is the modern standard.

npm create vue@latest
# Follow prompts (Select TypeScript, Vue Router, Pinia, etc.)
cd my-project && npm install && npm run dev

2. Reactivity Deep Dive

Vue 3 uses the Composition API and JavaScript Proxies to handle reactivity.

Declaring, Assigning, and Displaying Variables

  • ref(): Used for primitives (String, Number, Boolean). Creates a reactive reference. Access/assign via .value in JS, but directly in the template.
  • reactive(): Used for complex objects and arrays. No .value needed.
<script setup>
import { ref, reactive } from 'vue';

// 1. Declare
const count = ref(0); 
const user = reactive({ name: 'John', age: 30 });

// 2. Assign
const updateValues = () => {
  count.value = 5; // Use .value for refs in JS
  user.age = 31;   // Direct access for reactive
};
</script>

<template>
  <!-- 3. Display -->
  <div>Count is: {{ count }}</div>
  <div>User: {{ user.name }}</div>
</template>

Advanced Reactivity Concepts

  • computed(): Caches a calculated value based on reactive dependencies. Only re-evaluates when dependencies change.
  • watch(): Observes a specific reactive variable and triggers a callback when it changes. Good for side effects (e.g., API calls).
  • watchEffect(): Automatically tracks reactive variables used inside its callback and runs immediately.
  • shallowRef() / shallowReactive(): Only the top-level is reactive. Great for performance optimization with massive objects or large arrays where inner changes don't need tracking.
  • triggerRef(): Manually triggers updates for a shallowRef.
  • toRef() / toRefs(): Extracts properties from a reactive object while maintaining their reactivity (fixes destructuring reactivity loss).
  • customRef(): Allows creating custom reactivity logic, like a debounced input.
import { ref, computed, watch, toRefs, reactive, shallowRef } from 'vue';

const state = reactive({ search: '', page: 1 });
// Destructuring loses reactivity UNLESS we use toRefs
const { search, page } = toRefs(state);

// Computed Property
const isSearching = computed(() => search.value.length > 0);

// Watcher
watch(search, (newValue, oldValue) => {
  console.log(`Search changed from ${oldValue} to ${newValue}`);
});

// shallowRef for large data
const massiveDataset = shallowRef([]);
// Updating inner values doesn't trigger UI, must overwrite entirely:
// massiveDataset.value = [...newData]; 

3. Template Directives

Directives are special tokens in the markup that tell the library to do something to a DOM element.

Conditional Rendering & Dynamic Attributes

  • v-if / v-else: Physically adds/removes elements from the DOM. Higher toggle cost.
  • v-show: Toggles display: none. Elements stay in the DOM. Better for frequent toggling.
  • v-bind (or just `:`): Binds an HTML attribute to a JS variable.
<!-- Conditionally render -->
<div v-if="isLoggedIn">Welcome back!</div>
<div v-else>Please log in.</div>

<!-- Dynamic Attributes & Styling -->
<img :src="user.avatarUrl" :alt="user.name">

<!-- Conditional Classes and Inline Styles -->
<div :class="{ 'bg-red-500': hasError, 'text-white': true }"
     :style="{ fontSize: baseFontSize + 'px' }">
  Alert Box
</div>

Looping & Events

  • v-for: Loops over arrays or objects. Always use a unique :key for DOM rendering performance.
  • v-on (or just `@`): Listens for DOM events.
<!-- Array Loop with Index -->
<ul>
  <li v-for="(item, index) in itemsArray" :key="item.id">
    {{ index + 1 }}. {{ item.name }}
  </li>
</ul>

<!-- Object Loop -->
<div v-for="(value, key, index) in userObject" :key="key">
  {{ key }}: {{ value }}
</div>

<!-- Click Events with Modifiers -->
<button @click.prevent="submitData">Submit</button>

4. Components, Props & Communication

Props, Emits, and v-model on Components

Data flows down via props, and events flow up via emits. In Vue 3, v-model on components defaults to a prop named modelValue and an event named update:modelValue.

<!-- ChildComponent.vue -->
<script setup>
// 1. Define Props (Data from parent)
const props = defineProps({
  title: String,
  modelValue: String // Implicitly used by v-model
});

// 2. Define Emits (Events sent to parent)
const emit = defineEmits(['update:modelValue', 'custom-event']);

const updateInput = (event) => {
  emit('update:modelValue', event.target.value);
};
</script>

<template>
  <h3>{{ title }}</h3>
  <input :value="modelValue" @input="updateInput" />
  <button @click="emit('custom-event', 123)">Click Me</button>
  
  <!-- Slots allow injecting HTML from parent -->
  <slot name="footer">Default Footer</slot>
</template>

Advanced Component Patterns

  • Provide/Inject: Prop drilling solution. Parent uses provide('key', data), any deeply nested child uses inject('key').
  • defineExpose: Controls which methods/refs are public when a parent accesses the child via a Template Ref.
  • defineAsyncComponent: Lazy loads components. Great for code-splitting.
import { provide, inject, defineAsyncComponent } from 'vue';

// Parent provides:
provide('themeConfig', { dark: true });

// Deep Child injects:
const theme = inject('themeConfig');

// Lazy Loading a Heavy Component
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'));

5. Forms & Validation

Reading Inputs & Basic Validation

Use v-model for two-way binding on inputs. For validation, use computed properties or third-party libraries.

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

const email = ref('');
const password = ref('');
const formSubmitted = ref(false);

const isEmailValid = computed(() => email.value.includes('@'));
const isFormValid = computed(() => isEmailValid.value && password.value.length >= 6);

const handleSubmit = () => {
  formSubmitted.value = true;
  if (isFormValid.value) {
    alert('Form submitted: ' + email.value);
  }
};
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="email" type="email" placeholder="Email" />
    <span v-if="formSubmitted && !isEmailValid" class="error">Invalid Email</span>

    <input v-model="password" type="password" placeholder="Password" />
    
    <button :disabled="!isFormValid" type="submit">Submit</button>
  </form>
</template>

Advanced Forms

For complex, multi-step forms or heavy validation, libraries like VeeValidate or Vuelidate are standard. To do Debounced Validation, use customRef or libraries like Lodash debounce attached to a watch.

6. HTTP Requests & Data Fetching

Fetch API / Axios with Loading States

Always track isLoading and error states. To cancel requests on unmount, use an AbortController.

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

const data = ref(null);
const isLoading = ref(false);
const error = ref(null);
let controller;

const fetchData = async () => {
  isLoading.value = true;
  error.value = null;
  controller = new AbortController(); // For cancelling request

  try {
    const res = await fetch('https://api.example.com/data', { signal: controller.signal });
    if (!res.ok) throw new Error('Failed to fetch');
    data.value = await res.json();
  } catch (err) {
    if (err.name !== 'AbortError') error.value = err.message;
  } finally {
    isLoading.value = false;
  }
};

onMounted(fetchData);
onUnmounted(() => controller?.abort()); // Cancel on component unmount
</script>

Composables (useFetch)

You can extract the above logic into a reusable Composable (e.g., useFetch.js) to share stateful logic across multiple components.

7. Routing (Vue Router)

Vue Router manages Single Page Application (SPA) navigation. It provides components like <router-link> and <router-view>.

Navigation & Guards

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

const routes = [
  { path: '/', component: Home },
  // Lazy Loaded Route (Code Splitting)
  { path: '/about', component: () => import('./About.vue') },
  // Dynamic Params
  { path: '/user/:id', component: () => import('./User.vue'), props: true },
  // 404 Wildcard
  { path: '/:pathMatch(.*)*', component: () => import('./NotFound.vue') }
];

const router = createRouter({ history: createWebHistory(), routes });

// Global Navigation Guard (e.g., Authentication)
router.beforeEach((to, from, next) => {
  const isAuthenticated = localStorage.getItem('token');
  if (to.path === '/dashboard' && !isAuthenticated) next('/');
  else next();
});

export default router;

In components, use const router = useRouter() for programmatic navigation (router.push('/path')) and const route = useRoute() to access params (route.params.id).

8. State Management (Pinia)

Pinia is the official successor to Vuex. It provides type-safe, modular global state management without mutations.

// stores/counter.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useCounterStore = defineStore('counter', () => {
  // State
  const count = ref(0);
  
  // Getters
  const doubleCount = computed(() => count.value * 2);
  
  // Actions
  function increment() {
    count.value++;
  }

  return { count, doubleCount, increment };
});
<!-- Inside Component -->
<script setup>
import { useCounterStore } from '@/stores/counter';
const store = useCounterStore();
// Access: store.count, store.increment()
</script>

9. Performance & Optimization

  • v-once: Renders the element and children ONLY ONCE. Ignores subsequent data changes. Good for static text.
  • v-memo: Memoizes a sub-tree of the template. Only re-renders if the dependencies change (v-memo="[valueA, valueB]").
  • <keep-alive>: Wraps dynamic components (<component :is="..."> or router-views) to cache their state instead of destroying them.
    <router-view v-slot="{ Component }">
      <keep-alive>
        <component :is="Component" />
      </keep-alive>
    </router-view>
  • Virtual Scroller: For massive lists (10k+ items), do not use v-for directly. Use libraries like vue-virtual-scroller to only render elements currently visible in the DOM viewport.
  • Suspense: An experimental feature built-in to handle async dependencies (like async setup functions or lazy components) with a fallback #fallback template.

10. Testing, Ecosystem & Real-World

Lifecycle Hooks

Functions that allow you to tap into a component's lifecycle: onMounted (DOM is ready, good for API calls/DOM manipulation), onUpdated (reactive state changed DOM), onUnmounted (cleanup event listeners/intervals).

Testing (Vitest & Vue Test Utils)

import { mount } from '@vue/test-utils';
import MyComponent from './MyComponent.vue';
import { expect, test } from 'vitest';

test('renders text and clicks', async () => {
  const wrapper = mount(MyComponent, { props: { msg: 'Hello' } });
  
  // Assert
  expect(wrapper.text()).toContain('Hello');
  
  // Trigger Event
  await wrapper.find('button').trigger('click');
  expect(wrapper.emitted()).toHaveProperty('custom-event');
});

TypeScript Integration

Vue 3 was built in TypeScript. Use <script setup lang="ts">. You can strongly type props, refs, and emits.

const count = ref<number>(0);
const props = defineProps<{
  id: string;
  items: Array<{ name: string, value: number }>
}>();

Environment Variables

In Vite, create .env files. Variables must be prefixed with VITE_ to be exposed to the client. Access them via import.meta.env.VITE_API_URL. In Vue CLI (Webpack), they were prefixed with VUE_APP_ and accessed via process.env.

Dark Mode, Web Sockets, and Deployment

  • Dark Mode: Bind a class to the root <html> tag based on a Pinia state, and use Tailwind's dark: modifier.
  • WebSockets: Initialize new WebSocket() inside onMounted, and ensure you close it inside onUnmounted.
  • Deployment: Run npm run build. This generates a dist folder containing static HTML/CSS/JS. You can upload this folder to Netlify, Vercel, or GitHub Pages. Ensure you configure redirect rules (_redirects or vercel.json) to point all routes to index.html to prevent 404s on refresh (SPA requirement).

Post a Comment

Previous Post Next Post