DEV Community

Cover image for Learning JS frameworks with me(part 3):Vue.js- The Progressive JavaScript Framework
Shaman Shetty
Shaman Shetty

Posted on

1 1

Learning JS frameworks with me(part 3):Vue.js- The Progressive JavaScript Framework

I've been taking you along on my journey through JavaScript frameworks, and so far we've explored vanilla JavaScript, jQuery, angular.js. Today, we're diving into Vue.js—a framework that strikes a beautiful balance between simplicity and power.

When I first encountered Vue, I was immediately drawn to its gentle learning curve and intuitive reactivity system. As someone who had worked extensively with jQuery, Vue felt like a natural evolution rather than a jarring paradigm shift.

Let's explore what makes Vue special, why it's worth learning in 2025, and how to build your first Vue application.

The Rise of Vue.js

Vue.js was created by Evan You in 2014 while he was working at Google. Frustrated by the complexity of Angular but impressed by its data-binding capabilities, Evan set out to create something that combined the best parts of Angular with a lighter weight, more approachable API.

What started as a personal project has grown into one of the most popular frontend frameworks in the world. Vue consistently ranks in the top 3 most-loved frontend frameworks in developer surveys, and powers everything from small personal websites to enterprise applications at companies like Alibaba, Xiaomi, and Adobe.

Why Vue.js Matters in 2025

With so many frameworks to choose from, you might wonder why Vue deserves your attention. Here's why I believe Vue remains relevant and valuable:

  1. Progressive Architecture: Unlike some frameworks that require you to go all-in, Vue can be adopted incrementally. I've successfully integrated Vue into legacy jQuery codebases, adding reactivity one component at a time.

  2. Balanced Learning Curve: Vue strikes the perfect balance between simplicity and power. You can get started with basic directives and components, then gradually adopt more advanced features as you need them.

  3. Single-File Components: Vue's .vue files elegantly combine template, logic, and styling in one cohesive unit, making components easier to understand and maintain.

  4. Powerful Ecosystem: Vue Core, Vue Router, Vuex (or Pinia), and Vue DevTools form a complete ecosystem for building sophisticated applications.

  5. Strong Community: The Vue community is known for being welcoming and supportive, with excellent documentation and learning resources.

  6. Vue 3 Innovations: With the Composition API, improved TypeScript support, and performance enhancements, Vue 3 keeps the framework modern and competitive.

Setting Up Your First Vue Project

Let's get hands-on and build the same task application we created with vanilla JS and jQuery, but this time with Vue. We'll start with Vue's CDN version for simplicity, then mention how you'd set up a more robust project with Vue CLI or Vite.

Step 1: Create Your Project Structure

vue-project/
├── index.html
├── css/
│   └── style.css
└── js/
    └── main.js
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Your HTML File

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Task List</title>
    <link rel="stylesheet" href="css/style.css">
    <!-- Vue 3 from CDN -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
    <header>
        <h1>My Vue Task App</h1>
    </header>

    <main>
        <section class="todo-app">
            <div id="app">
                <h2>Task List</h2>
                <form @submit.prevent="addTask">
                    <input type="text" v-model="newTask" placeholder="Add a new task..." required>
                    <button type="submit">Add Task</button>
                </form>

                <div v-if="tasks.length > 0" class="filter-controls">
                    <button 
                        v-for="option in filterOptions" 
                        :key="option.value"
                        @click="currentFilter = option.value"
                        :class="['filter-btn', { active: currentFilter === option.value }]"
                    >
                        {{ option.label }}
                    </button>
                </div>

                <ul id="task-list">
                    <li v-for="task in filteredTasks" :key="task.id" :data-id="task.id">
                        <span 
                            :class="{ completed: task.completed }"
                            @click="toggleTask(task)"
                        >
                            {{ task.text }}
                        </span>
                        <button class="delete-btn" @click="deleteTask(task.id)">Delete</button>
                    </li>
                </ul>
            </div>
        </section>
    </main>

    <script src="js/main.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Step 3: Use The Same CSS

We'll continue using the same CSS from our previous examples to maintain visual consistency. This helps highlight the differences in the JavaScript approach rather than the styling.

Step 4: Vue Magic in the JavaScript File

Now, here's where Vue really shines. Let's write the JavaScript code to power our task manager:

// js/main.js
const { createApp, ref, computed, onMounted, watch } = Vue;

const app = createApp({
    setup() {
        // Reactive state with ref
        const newTask = ref('');
        const tasks = ref([]);
        const currentFilter = ref('all');

        const filterOptions = [
            { label: 'All', value: 'all' },
            { label: 'Active', value: 'active' },
            { label: 'Completed', value: 'completed' }
        ];

        // Load tasks from localStorage
        onMounted(() => {
            const savedTasks = JSON.parse(localStorage.getItem('tasks')) || [];
            tasks.value = savedTasks;
        });

        // Watch for changes and save to localStorage
        watch(tasks, (newTasks) => {
            localStorage.setItem('tasks', JSON.stringify(newTasks));
        }, { deep: true });

        // Computed property for filtered tasks
        const filteredTasks = computed(() => {
            if (currentFilter.value === 'active') {
                return tasks.value.filter(task => !task.completed);
            } else if (currentFilter.value === 'completed') {
                return tasks.value.filter(task => task.completed);
            }
            return tasks.value;
        });

        // Methods
        function addTask() {
            const taskText = newTask.value.trim();
            if (taskText === '') return;

            tasks.value.push({
                id: Date.now(),
                text: taskText,
                completed: false
            });

            newTask.value = '';
        }

        function toggleTask(task) {
            task.completed = !task.completed;
        }

        function deleteTask(id) {
            tasks.value = tasks.value.filter(task => task.id !== id);
        }

        // Return everything needed in the template
        return {
            newTask,
            tasks,
            currentFilter,
            filterOptions,
            filteredTasks,
            addTask,
            toggleTask,
            deleteTask
        };
    }
}).mount('#app');
Enter fullscreen mode Exit fullscreen mode

For those who prefer the Options API (which might feel more familiar if you're coming from jQuery), here's the same functionality:

// Options API alternative
const app = createApp({
    data() {
        return {
            newTask: '',
            tasks: [],
            currentFilter: 'all',
            filterOptions: [
                { label: 'All', value: 'all' },
                { label: 'Active', value: 'active' },
                { label: 'Completed', value: 'completed' }
            ]
        };
    },

    computed: {
        filteredTasks() {
            if (this.currentFilter === 'active') {
                return this.tasks.filter(task => !task.completed);
            } else if (this.currentFilter === 'completed') {
                return this.tasks.filter(task => task.completed);
            }
            return this.tasks;
        }
    },

    methods: {
        addTask() {
            const taskText = this.newTask.trim();
            if (taskText === '') return;

            this.tasks.push({
                id: Date.now(),
                text: taskText,
                completed: false
            });

            this.newTask = '';
        },

        toggleTask(task) {
            task.completed = !task.completed;
        },

        deleteTask(id) {
            this.tasks = this.tasks.filter(task => task.id !== id);
        }
    },

    watch: {
        tasks: {
            handler(newTasks) {
                localStorage.setItem('tasks', JSON.stringify(newTasks));
            },
            deep: true
        }
    },

    mounted() {
        const savedTasks = JSON.parse(localStorage.getItem('tasks')) || [];
        this.tasks = savedTasks;
    }
}).mount('#app');
Enter fullscreen mode Exit fullscreen mode

The Vue Difference: Declarative Rendering & Reactivity

If you compare this code to our jQuery version, the differences are striking. Let's break down what makes Vue fundamentally different:

1. Declarative vs Imperative

jQuery (Imperative):

function renderTasks() {
    $taskList.empty();
    tasks.forEach(task => {
        const $li = $('<li>').attr('data-id', task.id);
        const $span = $('<span>').text(task.text);
        if (task.completed) {
            $span.addClass('completed');
        }
        // ... more DOM manipulation
    });
}
Enter fullscreen mode Exit fullscreen mode

Vue (Declarative):

<ul id="task-list">
    <li v-for="task in filteredTasks" :key="task.id" :data-id="task.id">
        <span :class="{ completed: task.completed }">
            {{ task.text }}
        </span>
        <button class="delete-btn" @click="deleteTask(task.id)">Delete</button>
    </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

With jQuery, we had to explicitly tell the browser how to update the DOM. With Vue, we describe what the DOM should look like based on our data, and Vue handles the updates automatically.

2. Reactive State Management

In our jQuery version, we had to manually call renderTasks() whenever our data changed. With Vue, changes to the data automatically trigger DOM updates thanks to its reactivity system.

3. Two-Way Binding

jQuery:

$taskForm.on('submit', function(e) {
    e.preventDefault();
    const taskText = $taskInput.val().trim();
    // ... add task
    $taskInput.val('').focus();
});
Enter fullscreen mode Exit fullscreen mode

Vue:

<input type="text" v-model="newTask" placeholder="Add a new task..." required>
Enter fullscreen mode Exit fullscreen mode

The v-model directive automatically synchronizes the input field with the newTask state variable, eliminating the need to manually get and set values.

4. Event Handling

jQuery:

$taskList.on('click', '.delete-btn', function() {
    const taskId = parseInt($(this).parent().data('id'));
    // ... delete task
});
Enter fullscreen mode Exit fullscreen mode

Vue:

<button class="delete-btn" @click="deleteTask(task.id)">Delete</button>
Enter fullscreen mode Exit fullscreen mode

Vue's event handling is more direct and less error-prone, with clear connections between the event and the handler function.

Key Vue.js Concepts

Let's explore some of the fundamental concepts that make Vue so powerful:

1. The Vue Instance

Every Vue application starts with creating a Vue instance (or "app" in Vue 3):

const app = Vue.createApp({
    // options
}).mount('#app');
Enter fullscreen mode Exit fullscreen mode

This creates a Vue instance and mounts it to the DOM element with ID "app". Everything inside this element is now reactive.

2. Directives

Vue uses special attributes called directives to apply reactive behavior to the DOM:

  • v-for: Loop through arrays or objects
  • v-if/v-else/v-show: Conditional rendering
  • v-bind or :: Bind attributes to dynamic values
  • v-on or @: Attach event listeners
  • v-model: Two-way data binding for form inputs

These directives are what give Vue its declarative power.

3. Components

While our simple example doesn't use components, they're a central concept in Vue:

// TaskItem.js component
app.component('task-item', {
    props: ['task'],
    template: `
        <li :data-id="task.id">
            <span 
                :class="{ completed: task.completed }"
                @click="$emit('toggle', task)"
            >
                {{ task.text }}
            </span>
            <button class="delete-btn" @click="$emit('delete', task.id)">Delete</button>
        </li>
    `
});
Enter fullscreen mode Exit fullscreen mode

Then in the parent template:

<task-item 
    v-for="task in filteredTasks"
    :key="task.id"
    :task="task"
    @toggle="toggleTask"
    @delete="deleteTask"
></task-item>
Enter fullscreen mode Exit fullscreen mode

Components allow you to split your UI into reusable pieces, each with its own logic and styling.

4. Computed Properties

Computed properties allow you to create derived values that update automatically when their dependencies change:

const filteredTasks = computed(() => {
    if (currentFilter.value === 'active') {
        return tasks.value.filter(task => !task.completed);
    } else if (currentFilter.value === 'completed') {
        return tasks.value.filter(task => task.completed);
    }
    return tasks.value;
});
Enter fullscreen mode Exit fullscreen mode

Every time tasks or currentFilter changes, filteredTasks recalculates automatically.

5. Watchers

Watchers let you perform side effects when reactive data changes:

watch(tasks, (newTasks) => {
    localStorage.setItem('tasks', JSON.stringify(newTasks));
}, { deep: true });
Enter fullscreen mode Exit fullscreen mode

This watches the tasks array and saves it to localStorage whenever it changes.

Building More Complex Applications with Vue

For real-world applications, you'd typically use Vue CLI or Vite to set up a more structured project:

# Using Vue CLI
npm install -g @vue/cli
vue create my-vue-project

# Or using Vite (recommended for new projects)
npm create vite@latest my-vue-project -- --template vue
Enter fullscreen mode Exit fullscreen mode

This gives you:

  • A proper build system with webpack or Vite
  • Hot module replacement for faster development
  • Support for Single-File Components (.vue files)
  • Pre-configured ESLint and formatting
  • Easy integration with Vue Router, Vuex/Pinia, and testing tools

A Single-File Component version of our task item might look like:

<!-- TaskItem.vue -->
<template>
  <li :data-id="task.id" class="task-item">
    <span 
      :class="{ completed: task.completed }"
      @click="$emit('toggle', task)"
    >
      {{ task.text }}
    </span>
    <button class="delete-btn" @click="$emit('delete', task.id)">Delete</button>
  </li>
</template>

<script>
export default {
  props: {
    task: {
      type: Object,
      required: true
    }
  }
}
</script>

<style scoped>
.task-item {
  display: flex;
  justify-content: space-between;
  padding: 12px;
  border-bottom: 1px solid #eee;
}

.completed {
  text-decoration: line-through;
  color: #888;
}

.delete-btn {
  background: #ff4757;
  color: white;
  border: none;
  border-radius: 4px;
  padding: 4px 8px;
  cursor: pointer;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Vue.js Ecosystem

Vue's ecosystem has grown impressively over the years:

Official Libraries

  • Vue Router: For building single-page applications with routing
  • Vuex/Pinia: State management libraries for complex applications
  • Vue Test Utils: Official testing utility library
  • Vue DevTools: Browser extension for debugging Vue applications

Popular Third-Party Libraries

  • Vuetify: Material Design component framework
  • Quasar: Build responsive websites, PWAs, and mobile apps from a single codebase
  • Vueuse: Collection of essential Vue composition utilities
  • Nuxt.js: Framework for creating server-rendered Vue applications

When to Use Vue.js in 2025

After working with Vue and other frameworks, here's my practical advice on when Vue makes the most sense:

Use Vue When:

  • You need a balanced framework with an easy learning curve
  • You're transitioning from jQuery to a modern framework
  • You want a clean separation of concerns with single-file components
  • You need to integrate modern features into legacy applications
  • You prefer a flexible, incrementally adoptable framework
  • You want excellent documentation and a supportive community

Consider Alternatives When:

  • You need an opinionated, batteries-included framework (consider Angular)
  • You're joining a team that already uses React or Angular extensively
  • You need the widest possible job market (React still leads in job postings)
  • You're building a native mobile app (React Native might be better)

From jQuery to Vue: The Evolution

What I find fascinating is how Vue represents a conceptual evolution from jQuery:

  • jQuery: Simplified DOM selection and manipulation
  • Vue: Declarative rendering and reactivity

Where jQuery made it easier to work with the DOM imperatively, Vue abstracts away DOM manipulation entirely, letting you focus on your data and business logic.

// jQuery: Imperative DOM updates
$("#count").text(count);
$("#button").on("click", function() {
  count++;
  $("#count").text(count);
});

// Vue: Declarative rendering with automatic updates
<div>{{ count }}</div>
<button @click="count++">Increment</button>
Enter fullscreen mode Exit fullscreen mode

Conclusion: Vue's Elegant Balance

Working with Vue has been a revelation for me. It offers the perfect balance between simplicity and power, with a gentle learning curve that doesn't sacrifice advanced capabilities.

What makes Vue special is how it manages to be both approachable for beginners and powerful enough for experts. You can start with the basics and progressively adopt more advanced features as you grow.

The concepts we've learned—declarative rendering, reactivity, component-based architecture—aren't just Vue concepts. They're fundamental to modern frontend development across frameworks. Understanding Vue gives you a solid foundation that will serve you well regardless of which framework you use in the future.

In my next post, we'll explore React and how it approaches many of the same problems with a different philosophy. Stay tuned as we continue our journey through modern JavaScript frameworks!

Have you used Vue.js? What was your experience with it compared to jQuery or other frameworks? I'd love to hear about your experiences in the comments!

Happy coding!

Top comments (2)

Collapse
 
alexi_pawelec_1d16f8888a1 profile image
Alexi Pawelec •

I think there is no reason to use pure vue outside of nuxt.

Collapse
 
alexi_pawelec_1d16f8888a1 profile image
Alexi Pawelec •

Maybe for some browser extensions, but still, nuxt automates many things, and we dont need to spend time on for example configuring routes.