javascriptroom guide

Developing Dynamic Web Apps with Vue.js: A Beginner's Tutorial

Vue.js (pronounced "view") is an open-source JavaScript framework for building user interfaces. Created by Evan You in 2014, Vue has gained traction for its: - **Simplicity**: Its syntax is HTML-based, making it easy to learn for developers familiar with HTML/CSS/JS. - **Reactivity**: Vue automatically updates the DOM when your data changes, eliminating manual DOM manipulation. - **Component-Based Structure**: Break your app into reusable, self-contained components (e.g., buttons, forms, cards). - **Flexibility**: Use Vue for small projects (e.g., enhancing a static page) or large-scale apps with routing and state management. Vue’s渐进式 (progressive) nature means you can start with basic features (like reactivity and components) and add advanced tools (routing, state management) as your app grows. This makes it ideal for beginners.

Vue.js has rapidly become one of the most popular JavaScript frameworks for building dynamic web applications, thanks to its simplicity, flexibility, and gentle learning curve. Unlike monolithic frameworks that require you to adopt an entire ecosystem, Vue is progressive—meaning you can start small and scale up as needed. Whether you’re a complete beginner or transitioning from other frameworks like React or Angular, Vue.js makes it easy to build interactive, modern web apps.

In this tutorial, we’ll walk through the fundamentals of Vue.js, from setting up your environment to building a fully functional dynamic app. By the end, you’ll have the skills to create your own Vue-powered projects and understand core concepts like reactivity, components, routing, and state management.

Table of Contents

  1. Introduction to Vue.js
  2. Setting Up Your Development Environment
  3. Vue.js Fundamentals
  4. Component-Based Architecture
  5. Building a Simple Dynamic App: Todo List
  6. Client-Side Routing with Vue Router
  7. State Management with Pinia
  8. Deploying Your Vue App
  9. Conclusion
  10. References

Setting Up Your Development Environment

Before diving into Vue, you’ll need to set up your development environment. Here’s what you need:

Prerequisites

  • Basic knowledge of HTML, CSS, and JavaScript.
  • Node.js (v14.0.0+ recommended) and npm (Node Package Manager), which comes with Node.js.

To check if Node.js is installed, run:

node -v  
npm -v  

If not installed, download Node.js from nodejs.org.

Installing Vue

Vue offers two main tools for project setup:

Vite is a fast build tool that replaces the older Vue CLI. It’s optimized for speed and modern workflows.

To create a new Vue project with Vite:

npm create vite@latest my-vue-app -- --template vue  
  • Follow the prompts: Enter your project name (e.g., my-vue-app), select “Vue” as the framework, and “JavaScript” as the variant.
  • Navigate to the project folder and install dependencies:
    cd my-vue-app  
    npm install  
  • Start the development server:
    npm run dev  

Your app will run at http://localhost:5173. Open this in your browser to see a default Vue welcome page.

2. Vue CLI (Legacy, Still Supported)

If you prefer the older CLI (not recommended for new projects), install it globally first:

npm install -g @vue/cli  

Then create a project:

vue create my-vue-app  

Vue.js Fundamentals

Let’s start with the core concepts of Vue.js.

The Vue Instance

At the heart of every Vue app is a Vue instance—an object that connects your data to the DOM. In a Vite project, this is managed automatically in src/main.js, but let’s simplify to understand the basics.

A basic Vue instance looks like this:

const app = Vue.createApp({  
  data() {  
    return {  
      message: "Hello, Vue!"  
    };  
  }  
});  

app.mount("#app"); // Mounts the app to the DOM element with id="app"  
  • data(): Returns an object of reactive data properties. When these properties change, the DOM updates automatically.
  • mount("#app"): Attaches the Vue instance to the HTML element with id="app".

Templates and Directives

Vue uses templates (HTML with special syntax) to define the UI. Templates can include directives—special attributes prefixed with v- that add reactivity to the DOM.

Common Directives:

  • v-text: Sets the text content of an element.

    <p v-text="message"></p> <!-- Equivalent to <p>{{ message }}</p> -->  
  • v-bind: Binds an HTML attribute to a data property (shorthand: :).

    <img :src="imageUrl" :alt="message">  
  • v-on: Listens to DOM events (shorthand: @).

    <button @click="count++">Click me</button>  
  • v-model: Creates two-way data binding for form inputs (syncs input value with data).

    <input v-model="username" placeholder="Enter name">  
    <p>Hello, {{ username }}!</p>  
  • v-for: Renders a list based on an array.

    <ul>  
      <li v-for="item in items" :key="item.id">{{ item.name }}</li>  
    </ul>  
  • v-if/v-else: Conditionally renders elements.

    <p v-if="isLoggedIn">Welcome back!</p>  
    <p v-else>Please log in.</p>  

Reactivity: How Vue Updates the DOM

Vue’s reactivity system tracks changes to data properties and updates the DOM automatically. Here’s how it works:

  1. When you create a Vue instance, Vue converts all properties in data() into reactive getters/setters.
  2. When a component renders, Vue tracks which data properties are used (dependency tracking).
  3. When a reactive property changes, Vue re-renders the components that depend on it (re-rendering).

Example: A simple counter with reactivity:

const app = Vue.createApp({  
  data() {  
    return { count: 0 };  
  },  
});  

app.mount("#app");  
<div id="app">  
  <p>Count: {{ count }}</p>  
  <button @click="count++">Increment</button>  
</div>  

Clicking the button updates count, and Vue automatically updates the <p> tag.

Component-Based Architecture

Vue apps are built with components—reusable, self-contained pieces of UI. Components make code modular, easier to test, and scalable.

Creating Your First Component

Components can be global (available everywhere) or local (only in the parent component that defines them).

Global Component Example:

In src/main.js, register a global component:

import { createApp } from 'vue';  
import App from './App.vue';  
import HelloWorld from './components/HelloWorld.vue';  

const app = createApp(App);  
app.component('hello-world', HelloWorld); // Global component  
app.mount('#app');  

Now use it in any template:

<hello-world></hello-world>  

Local Component Example:

Define a component locally in another component (e.g., App.vue):

<template>  
  <div>  
    <local-component></local-component>  
  </div>  
</template>  

<script>  
import LocalComponent from './components/LocalComponent.vue';  

export default {  
  components: {  
    LocalComponent // Local to App.vue  
  }  
};  
</script>  

Props: Passing Data to Components

Props allow you to pass data from a parent component to a child component. They are defined in the child component and passed via attributes in the parent.

Child Component (TodoItem.vue):

<template>  
  <li>{{ todo.text }}</li>  
</template>  

<script>  
export default {  
  props: {  
    todo: {  
      type: Object, // Validate prop type  
      required: true // Mark as required  
    }  
  }  
};  
</script>  

Parent Component:

<template>  
  <ul>  
    <todo-item :todo="item" v-for="item in todos" :key="item.id"></todo-item>  
  </ul>  
</template>  

<script>  
import TodoItem from './TodoItem.vue';  

export default {  
  components: { TodoItem },  
  data() {  
    return {  
      todos: [  
        { id: 1, text: "Learn Vue" },  
        { id: 2, text: "Build an app" }  
      ]  
    };  
  }  
};  
</script>  

Events: Communicating Child to Parent

To send data from a child to a parent, use custom events. The child emits an event with $emit, and the parent listens with @event-name.

Child Component (TodoItem.vue):

<template>  
  <li>  
    {{ todo.text }}  
    <button @click="deleteTodo">×</button>  
  </li>  
</template>  

<script>  
export default {  
  props: { todo: { type: Object, required: true } },  
  methods: {  
    deleteTodo() {  
      this.$emit('delete-todo', this.todo.id); // Emit event with todo ID  
    }  
  }  
};  
</script>  

Parent Component:

<template>  
  <ul>  
    <todo-item  
      :todo="item"  
      v-for="item in todos"  
      :key="item.id"  
      @delete-todo="removeTodo"  
    ></todo-item>  
  </ul>  
</template>  

<script>  
export default {  
  methods: {  
    removeTodo(todoId) {  
      this.todos = this.todos.filter(todo => todo.id !== todoId);  
    }  
  }  
};  
</script>  

Building a Simple Dynamic App: Todo List

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

  • A form to add new todos.
  • A list of todos with delete buttons.
  • Component-based structure.

Project Setup

If you haven’t already, create a new Vite project:

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

Creating the Todo Components

1. TodoInput.vue (Child Component):

Handles the input form to add new todos.

<template>  
  <form @submit.prevent="addTodo">  
    <input  
      v-model="newTodoText"  
      placeholder="Add a new todo..."  
      required  
    >  
    <button type="submit">Add</button>  
  </form>  
</template>  

<script>  
export default {  
  data() {  
    return { newTodoText: "" };  
  },  
  methods: {  
    addTodo() {  
      this.$emit("add-todo", this.newTodoText); // Emit new todo text  
      this.newTodoText = ""; // Clear input  
    }  
  }  
};  
</script>  

2. TodoList.vue (Parent Component):

Renders the list of todos and coordinates with TodoInput and TodoItem.

<template>  
  <div class="todo-list">  
    <h1>Todo List</h1>  
    <TodoInput @add-todo="handleAddTodo" />  
    <ul>  
      <TodoItem  
        v-for="todo in todos"  
        :key="todo.id"  
        :todo="todo"  
        @delete-todo="handleDeleteTodo"  
      />  
    </ul>  
  </div>  
</template>  

<script>  
import TodoInput from './TodoInput.vue';  
import TodoItem from './TodoItem.vue';  

export default {  
  components: { TodoInput, TodoItem },  
  data() {  
    return {  
      todos: [  
        { id: 1, text: "Learn Vue components" },  
        { id: 2, text: "Build a todo app" }  
      ],  
      nextId: 3  
    };  
  },  
  methods: {  
    handleAddTodo(text) {  
      this.todos.push({ id: this.nextId++, text });  
    },  
    handleDeleteTodo(todoId) {  
      this.todos = this.todos.filter(todo => todo.id !== todoId);  
    }  
  }  
};  
</script>  

3. TodoItem.vue (Child Component):

Renders a single todo item with a delete button.

<template>  
  <li>  
    {{ todo.text }}  
    <button @click="deleteTodo">×</button>  
  </li>  
</template>  

<script>  
export default {  
  props: {  
    todo: { type: Object, required: true }  
  },  
  methods: {  
    deleteTodo() {  
      this.$emit('delete-todo', this.todo.id);  
    }  
  }  
};  
</script>  

<style scoped>  
li {  
  display: flex;  
  justify-content: space-between;  
  margin: 0.5rem 0;  
  padding: 0.5rem;  
  border: 1px solid #ddd;  
}  
button {  
  background: #ff4444;  
  color: white;  
  border: none;  
  border-radius: 50%;  
  cursor: pointer;  
}  
</style>  

4. Update App.vue:

Import TodoList and use it as the main component.

<template>  
  <div id="app">  
    <TodoList />  
  </div>  
</template>  

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

export default {  
  components: { TodoList }  
};  
</script>  

<style>  
#app {  
  max-width: 600px;  
  margin: 2rem auto;  
  padding: 0 1rem;  
  font-family: Arial, sans-serif;  
}  
</style>  

Adding Functionality: Add/Delete Todos

Test your app by adding todos and clicking the delete button. The TodoInput emits an add-todo event with the text, and TodoList adds it to the todos array. TodoItem emits delete-todo when the button is clicked, and TodoList filters it out.

Client-Side Routing with Vue Router

Most dynamic apps need multiple pages (e.g., home, about, settings). Vue Router handles client-side routing, enabling navigation without reloading the page.

Installing Vue Router

In your todo-app project, install Vue Router:

npm install vue-router@4  

Setting Up Routes

1. Create a router folder and index.js:

// src/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;  

2. Create views Folder:

  • src/views/Home.vue: Contains the TodoList component.

    <template>  
      <TodoList />  
    </template>  
    
    <script>  
    import TodoList from '../components/TodoList.vue';  
    
    export default {  
      components: { TodoList }  
    };  
    </script>  
  • src/views/About.vue: A simple about page.

    <template>  
      <div class="about">  
        <h1>About This App</h1>  
        <p>A simple todo app built with Vue.js!</p>  
      </div>  
    </template>  

3. Update main.js to Use Router:

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

createApp(App)  
  .use(router) // Use the router  
  .mount('#app');  

Update App.vue to include navigation links and a router view:

<template>  
  <div id="app">  
    <nav>  
      <router-link to="/">Home</router-link> |  
      <router-link to="/about">About</router-link>  
    </nav>  
    <router-view /> <!-- Renders the current route component -->  
  </div>  
</template>  

<style>  
/* Add styling for navigation links */  
nav {  
  margin: 2rem 0;  
}  

router-link {  
  margin-right: 1rem;  
  text-decoration: none;  
  color: #42b983; /* Vue's brand color */  
}  

router-link.router-link-exact-active {  
  font-weight: bold;  
  text-decoration: underline;  
}  
</style>  

Now you can navigate between the Home (todo list) and About pages!

State Management with Pinia

As your app grows, you may need to share state across components (e.g., user auth, global settings). Pinia is Vue’s official state management library (replaces Vuex) and simplifies managing shared state.

What is Pinia?

Pinia provides a centralized store for reactive state that can be accessed by any component. Key features:

  • Simple API with no nested modules.
  • TypeScript support.
  • DevTools integration.

Creating a Pinia Store for Todos

Let’s refactor our todo app to use Pinia, so the todos state is shared across components.

1. Install Pinia:

npm install pinia  

2. Update main.js to Use Pinia:

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

const app = createApp(App);  
app.use(createPinia()); // Use Pinia  
app.use(router);  
app.mount('#app');  

3. Create a stores Folder and todoStore.js:

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

// Define a store with the id 'todo'  
export const useTodoStore = defineStore('todo', {  
  state: () => ({  
    todos: [  
      { id: 1, text: "Learn Pinia" },  
      { id: 2, text: "Refactor todo app" }  
    ],  
    nextId: 3  
  }),  
  actions: {  
    addTodo(text) {  
      this.todos.push({ id: this.nextId++, text });  
    },  
    deleteTodo(todoId) {  
      this.todos = this.todos.filter(todo => todo.id !== todoId);  
    }  
  }  
});  

4. Update TodoList.vue to Use the Store:

Replace local todos state with the Pinia store.

<template>  
  <div class="todo-list">  
    <h1>Todo List</h1>  
    <TodoInput @add-todo="store.addTodo" />  
    <ul>  
      <TodoItem  
        v-for="todo in store.todos"  
        :key="todo.id"  
        :todo="todo"  
        @delete-todo="store.deleteTodo"  
      />  
    </ul>  
  </div>  
</template>  

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

export default {  
  components: { TodoInput, TodoItem },  
  setup() {  
    const store = useTodoStore(); // Access the store  
    return { store };  
  }  
};  
</script>  

Now the todos state is managed globally, making it easy to access from other components (e.g., the About page could display the todo count).

Deploying Your Vue App

To share your app with the world, build a production-ready version and deploy it.

Build the Project

Run the build command to generate optimized assets in the dist folder:

npm run build  

Deployment Options

Popular free options:

  • Netlify: Drag-and-drop the dist folder or connect to GitHub for auto-deployment.
  • Vercel: Similar to Netlify, with seamless GitHub integration.
  • GitHub Pages: Deploy to a GitHub repo’s gh-pages branch (requires extra config for Vue Router).

For Netlify/Vercel:

  1. Push your code to GitHub.
  2. Connect your repo to Netlify/Vercel.
  3. Set the build command to npm run build and the publish directory to dist.

Conclusion

You’ve now learned the basics of building dynamic web apps with Vue.js! We covered:

  • Setting up a Vue project with Vite.
  • Core concepts: reactivity, directives, and components.
  • Building a todo app with component communication.
  • Adding routing with Vue Router.
  • Managing global state with Pinia.

Vue’s simplicity and flexibility make it a great choice for beginners and experts alike. To continue learning:

  • Explore the official Vue.js documentation.
  • Experiment with the Composition API (a more flexible alternative to the Options API used here).
  • Learn about testing Vue apps with tools like Cypress or Vitest.

References

Happy coding! 🚀