Table of Contents
- What is Vue.js?
- Setting Up Your Development Environment
- Vue.js Fundamentals
- Components: The Building Blocks
- State Management with Pinia
- Routing with Vue Router
- Advanced Concepts: Composition API & Reactivity
- Hands-On Project: Build a Todo App
- Testing Vue.js Applications
- Deployment
- Conclusion
- 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
setStateor 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
Option B: Vite (Recommended for New Projects)
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>:keyis 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
-
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'); -
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
-
Create Route Components (e.g.,
views/Home.vue,views/About.vue). -
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; -
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.setfor 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:
-
Build the Project:
npm run build # Generates a "dist" folder with optimized assets -
Push to GitHub:
Create a repo and push your code. -
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.