Vue.js Framework Architecture

Module 25: Frontend Frameworks & State Management

Introduction to Vue.js

Vue.js (commonly referred to as Vue) is a progressive JavaScript framework for building user interfaces. Unlike monolithic frameworks, Vue is designed to be incrementally adoptable, allowing you to use as much or as little of the framework as you need.

Vue was created by Evan You in 2014 after working with AngularJS at Google. He aimed to extract the parts he liked about Angular while creating a lightweight framework that was more approachable and flexible.

Key Characteristics of Vue.js

graph TD A[Vue.js] --> B[Core Library] A --> C[Ecosystem] B --> D[Reactivity System] B --> E[Component System] B --> F[Template Compiler] B --> G[Virtual DOM] C --> H[Vue Router] C --> I[Vuex/Pinia] C --> J[Vue CLI] C --> K[Vite] C --> L[Nuxt.js] style A fill:#42b883 style B fill:#64b687 style C fill:#64b687 style D fill:#85c79c style E fill:#85c79c style F fill:#85c79c style G fill:#85c79c style H fill:#a6d8b1 style I fill:#a6d8b1 style J fill:#a6d8b1 style K fill:#a6d8b1 style L fill:#a6d8b1 classDef default stroke:#333,stroke-width:2px;

Real-world analogy: If frameworks were vehicles, Vue would be like a modular electric vehicle – you can start with a basic model and add features as needed. It's easy to drive, energy-efficient, and you can customize it extensively without having to replace the entire vehicle.

Vue.js Version History

Understanding Vue's evolution helps to contextualize its architecture:

We'll be focusing on Vue 3 in this course, as it represents the current direction and best practices for Vue development.

Major Architectural Changes in Vue 3

Core Architecture

Vue's architecture consists of several key parts that work together to create a reactive, component-based system:

flowchart TB A[Declarative Templates] --> B[Template Compiler] B --> C[Render Functions] C --> D[Virtual DOM] D --> E[Actual DOM] F[Reactivity System] --> G[Component State] G --> C style A fill:#42b883 style B fill:#64b687 style C fill:#85c79c style D fill:#a6d8b1 style E fill:#c8e9d3 style F fill:#42b883 style G fill:#64b687 classDef default stroke:#333,stroke-width:2px;

Reactivity System

The reactivity system is at the heart of Vue. It automatically tracks dependencies between data and the DOM, updating the UI when data changes.

In Vue 3, the reactivity system is built using JavaScript Proxies, which intercept property access and modifications:

// Vue 3 Reactivity System (simplified)
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key);  // Track that this property was accessed
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      trigger(target, key);  // Trigger updates for components that depend on this property
      return true;
    }
  });
}

// Usage
const state = reactive({ count: 0 });
state.count++;  // Automatically triggers UI updates

Real-world analogy: The reactivity system works like a smart home. When you change the thermostat setting (data), sensors (dependency tracking) detect this change and automatically adjust the heating or cooling systems (DOM updates) without you having to manually control each component.

Template Compilation

Vue templates are HTML-based templates with special directives and syntax. The template compiler converts these templates into render functions that create Virtual DOM nodes:

// Vue Template
<div>
  <p>{{ message }}</p>
  <button v-if="showButton" @click="increment">
    Count: {{ count }}
  </button>
</div>

// Compiled to (simplified)
function render() {
  return h('div', [
    h('p', state.message),
    state.showButton 
      ? h('button', { onClick: increment }, `Count: ${state.count}`)
      : null
  ]);
}

This compilation can happen at build time (for better performance) or at runtime (for flexibility).

Virtual DOM

Vue uses a Virtual DOM to efficiently update the actual DOM. The Virtual DOM is a lightweight JavaScript representation of the real DOM:

  1. Render functions create Virtual DOM nodes
  2. When data changes, a new Virtual DOM tree is created
  3. Vue compares the new and old Virtual DOM trees (diffing)
  4. It applies only the necessary changes to the real DOM (patching)

Real-world analogy: Think of the Virtual DOM like an architect's blueprint. Instead of rebuilding an entire house (the DOM) when you want to make changes, you modify the blueprint (Virtual DOM) first, then the construction team (Vue's patching algorithm) reviews the differences and only replaces or modifies the necessary parts of the structure.

Component Architecture

Vue is built around the concept of components – reusable, self-contained pieces of code that encapsulate markup, styles, and logic.

Single-File Components (SFCs)

Vue's most distinctive feature is Single-File Components (SFCs) with the .vue file extension. These files contain template, script, and style blocks in one file:

<!-- Counter.vue -->
<template>
  <div class="counter">
    <h2>{{ title }}</h2>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  props: {
    title: {
      type: String,
      default: 'Counter'
    }
  },
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  }
}
</script>

<style scoped>
.counter {
  border: 1px solid #ccc;
  padding: 1rem;
  margin: 1rem 0;
}
</style>

SFCs provide several benefits:

Real-world analogy: SFCs are like prefabricated rooms for a house. Each room comes complete with walls (structure), electrical systems (logic), and decoration (styles), ready to be connected to other rooms to form a complete house (application).

Component Lifecycle

Vue components have a lifecycle with hooks that allow you to run code at specific stages:

graph TD A[Creation] --> B[beforeCreate] B --> C[created] C --> D[Mounting] D --> E[beforeMount] E --> F[mounted] F --> G[Updating] G --> H[beforeUpdate] H --> I[updated] I --> G I --> J[Unmounting] J --> K[beforeUnmount] K --> L[unmounted] style A fill:#42b883 style D fill:#42b883 style G fill:#42b883 style J fill:#42b883 classDef default stroke:#333,stroke-width:2px;
export default {
  // Creation hooks
  beforeCreate() {
    console.log('Component is being initialized');
  },
  created() {
    console.log('Component instance created');
    // Good place for API calls and data initialization
  },
  
  // Mounting hooks
  beforeMount() {
    console.log('Template compiled, about to mount');
  },
  mounted() {
    console.log('Component mounted to DOM');
    // Good place for DOM manipulations and integrations
  },
  
  // Updating hooks
  beforeUpdate() {
    console.log('Data changed, about to update DOM');
  },
  updated() {
    console.log('DOM updated after data change');
  },
  
  // Unmounting hooks
  beforeUnmount() {
    console.log('About to unmount component');
    // Clean up resources, event listeners, etc.
  },
  unmounted() {
    console.log('Component unmounted from DOM');
  }
}

Options API vs. Composition API

Vue 3 offers two ways to define component logic: the Options API (traditional) and the Composition API (new in Vue 3).

Options API

The Options API organizes code into object options like data, methods, and computed:

<script>
export default {
  name: 'UserProfile',
  
  // Data properties
  data() {
    return {
      user: null,
      loading: false,
      error: null
    }
  },
  
  // Computed properties
  computed: {
    fullName() {
      return this.user ? `${this.user.firstName} ${this.user.lastName}` : ''
    }
  },
  
  // Methods
  methods: {
    async fetchUser(id) {
      this.loading = true;
      try {
        const response = await fetch(`/api/users/${id}`);
        this.user = await response.json();
      } catch (err) {
        this.error = err.message;
      } finally {
        this.loading = false;
      }
    }
  },
  
  // Lifecycle hooks
  created() {
    this.fetchUser(this.$route.params.id);
  }
}
</script>

Composition API

The Composition API lets you organize code by logical concerns rather than option types:

<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';

// State
const user = ref(null);
const loading = ref(false);
const error = ref(null);

// Computed properties
const fullName = computed(() => {
  return user.value ? `${user.value.firstName} ${user.value.lastName}` : '';
});

// Methods
async function fetchUser(id) {
  loading.value = true;
  try {
    const response = await fetch(`/api/users/${id}`);
    user.value = await response.json();
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
}

// Lifecycle
const route = useRoute();
onMounted(() => {
  fetchUser(route.params.id);
});
</script>

Key benefits of the Composition API:

Real-world analogy: The Options API is like organizing your kitchen by types of items (all utensils in one drawer, all ingredients in one cabinet). The Composition API is like organizing by recipes or meal types, keeping all related items together regardless of their type.

Vue.js Ecosystem

Vue's ecosystem includes several official libraries and tools that extend its capabilities:

Core Libraries

Development Tools

Frameworks Built on Vue

Setting Up a Vue Project

There are several ways to start a Vue project, from simple to more complex:

Using CDN for Simple Projects

<!DOCTYPE html>
<html>
<head>
  <title>Vue 3 CDN Example</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
    <button @click="count++">Count: {{ count }}</button>
  </div>

  <script>
    const { createApp, ref } = Vue;
    
    createApp({
      setup() {
        const message = ref('Hello Vue 3!');
        const count = ref(0);
        
        return {
          message,
          count
        };
      }
    }).mount('#app');
  </script>
</body>
</html>

Using Vite (Recommended for Modern Development)

# Create a new Vue 3 project
npm create vite@latest my-vue-app -- --template vue

# Navigate to project folder
cd my-vue-app

# Install dependencies
npm install

# Start the development server
npm run dev

The project structure will look like:

my-vue-app/
├── public/              # Static assets that will be served as-is
├── src/
│   ├── assets/          # Dynamic assets that will be processed by the build system
│   ├── components/      # Vue components
│   │   └── HelloWorld.vue
│   ├── App.vue          # Root component
│   └── main.js          # Application entry point
├── index.html           # HTML entry point
├── package.json         # Dependencies and scripts
├── vite.config.js       # Vite configuration
└── README.md

Entry Point: main.js

import { createApp } from 'vue'
import App from './App.vue'

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

// Mount the app to a DOM element
app.mount('#app')

This simple structure can be extended with plugins and other features:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import App from './App.vue'
import routes from './routes'

// Create the app instance
const app = createApp(App)

// Create the router
const router = createRouter({
  history: createWebHistory(),
  routes
})

// Create the store
const pinia = createPinia()

// Add plugins to the app
app.use(router)
app.use(pinia)

// Mount the app
app.mount('#app')

How Vue Compares to Other Frameworks

Vue vs React

Vue vs Angular

Real-world usage tends to vary by project needs and team preferences. Vue is particularly strong in scenarios where:

Practice Activity

Set Up Your First Vue 3 Project

  1. Use Vite to create a new Vue 3 project
  2. Examine the project structure and understand each file's purpose
  3. Modify the App.vue file to create a simple counter application
  4. Implement both Options API and Composition API versions

For the Counter App:

Start with this template for the Options API version:

<template>
  <div class="counter-app">
    <h1>{{ title }}</h1>
    <p class="count">Count: {{ count }}</p>
    <p class="info">The count is currently {{ evenOrOdd }}</p>
    
    <div class="buttons">
      <!-- Add your buttons here -->
    </div>
  </div>
</template>

<script>
export default {
  name: 'CounterApp',
  props: {
    title: {
      type: String,
      default: 'Vue Counter'
    }
  },
  data() {
    return {
      count: 0
    }
  },
  computed: {
    // Add your computed property for evenOrOdd
  },
  methods: {
    // Add your methods for increment, decrement, and reset
  }
}
</script>

<style scoped>
.counter-app {
  max-width: 400px;
  margin: 0 auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
  text-align: center;
}

.count {
  font-size: 2rem;
  font-weight: bold;
}

.buttons {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}

button {
  padding: 8px 16px;
  background-color: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #33a06f;
}

button.reset {
  background-color: #ff7875;
}

button.reset:hover {
  background-color: #f5222d;
}
</style>

Then convert it to use the Composition API with the <script setup> syntax.

Key Takeaways

Additional Resources