Skip to content
Vue.js Basics Explained — Complete Beginner's Guide

Vue.js Basics Explained — Complete Beginner's Guide

DodaTech Updated Jun 6, 2026 15 min read

Vue.js is a progressive JavaScript framework that makes HTML reactive — your templates update automatically whenever your data changes, no manual DOM work needed.

What You’ll Learn

  • Set up Vue.js via CDN and Vite
  • Understand reactive data with the spreadsheet analogy
  • Use template syntax and directives (v-bind, v-if, v-for, v-on, v-model)
  • Create computed properties and watchers with real use cases
  • Master Vue’s lifecycle hooks
  • Avoid 6 common beginner mistakes
  • Build a complete interactive todo list

Why Vue Basics Matters

Every modern web app needs to update its UI when data changes. Without a framework, you’d manually select DOM elements and update them — error-prone and messy. Vue automates this with reactivity: change your data, and the UI updates instantly.

Security note: Understanding Vue Basics helps build more secure applications — a core principle at DodaTech, where tools like Durga Antivirus Pro and Doda Browser rely on solid implementation practices.

At DodaTech, Vue powers parts of the Doda Browser settings panel and the DodaZIP file manager interface, where real-time file status updates and reactive UI states are essential. Vue’s gentle learning curve makes it ideal for adding interactivity to existing pages without a full rewrite.

Your Learning Path

    flowchart LR
  A[JavaScript Basics] --> B[Vue Basics]
  B --> C[Vue Components]
  C --> D[Vue Router]
  D --> E[Vue Advanced]
  B:::current

  classDef current fill:#42b883,color:#fff,stroke:#3aa876,stroke-width:2px
  
Prerequisites: You need basic JavaScript (variables, functions, arrays, objects) and HTML (tags, attributes). No prior framework experience required.

Understanding Vue’s Core Idea — The Reactive Spreadsheet

Before writing code, let’s understand what makes Vue special.

Imagine a spreadsheet:

CellFormulaValue
A15
A210
A3=A1+A215

If you change A1 to 7, A3 automatically becomes 17. You didn’t recalculate — the spreadsheet did it.

Vue works the same way. You declare what depends on what, and Vue handles the rest.

  • ref() and reactive() create reactive cells (like A1, A2)
  • computed() creates formula cells (like =A1+A2)
  • watch() is like setting a surveillance camera — it alerts you when a value changes
  • The template renders automatically when any dependency changes

Keep this spreadsheet analogy in mind. It’s the single most important concept in Vue.

Setting Up Vue

Method 1: CDN (Quick Start, No Install)

<!-- The simplest way to try Vue: add a script tag.
     vue.global.js loads the full Vue library from a CDN.
     Perfect for prototyping or adding Vue to an existing page. -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

Method 2: Vite (Recommended for Real Projects)

# Vite creates a new Vue project with hot-reload and build tools.
# The --template vue flag scaffolds a Vue 3 project.
npm create vite@latest my-vue-app -- --template vue
cd my-vue-app
npm install          # Downloads all dependencies
npm run dev          # Starts a dev server with hot-reload
For this tutorial, use the CDN approach in a single HTML file. It removes setup friction so you can focus on learning Vue concepts. Once you’re comfortable, switch to Vite for real projects.

Your First Vue App

Let’s break down every line of this first example:

<!DOCTYPE html>
<html>
<head>
  <title>My First Vue App</title>
  <!-- Step 1: Load Vue from CDN.
       This makes the Vue object available globally. -->
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <!-- Step 2: The id="app" div is Vue's "territory."
       Vue will take over everything inside this element.
       Think of it as a stage where Vue performs. -->
  <div id="app">
    <!-- Step 3: {{ message }} is Vue's template syntax.
         It's like a placeholder — Vue replaces this text
         with the value of message. When message changes,
         this updates automatically. -->
    <h1>{{ message }}</h1>
  </div>

  <script>
    // Step 4: Get the createApp function from Vue.
    // createApp is the entry point for every Vue application.
    const { createApp } = Vue;

    // Step 5: Create a Vue app instance.
    // The object passed to createApp is called "options" —
    // it configures how the app behaves.
    createApp({
      // data() returns the app's reactive state.
      // "Reactive" means: when this value changes, the DOM updates.
      // data() must be a function (not a plain object) so each
      // component instance gets its own copy of the data.
      data() {
        return {
          message: "Hello Vue!"  // Any property returned here is reactive
        };
      }
    // .mount("#app") tells Vue: "take over the #app element
    // and render the template inside it."
    }).mount("#app");
  </script>
</body>
</html>

What happens when you open this: The page displays “Hello Vue!” in an <h1>. Open your browser console and type document.querySelector('#app').__vue_app__ — you’ll see the Vue instance. Try changing message via the console, and the DOM updates instantly.

Template Syntax — HTML with Superpowers

Text Interpolation

The {{ }} syntax is called text interpolation. It’s like a calculator display — whatever expression you put inside gets evaluated and converted to text:

<template>
  <p>{{ greeting }}</p>                              <!-- plain text variable -->
  <p>{{ number + 1 }}</p>                            <!-- math expression -->
  <p>{{ ok ? "YES" : "NO" }}</p>                     <!-- ternary operator -->
  <p>{{ message.split("").reverse().join("") }}</p>  <!-- string manipulation -->
</template>

<script>
export default {
  data() {
    return {
      greeting: "Hello",
      number: 5,
      ok: true,
      message: "Vue"
    };
  }
};
</script>

Why expressions, not statements? {{ }} evaluates to a value. You can’t use if or for inside — those belong in <script>. Think of {{ }} as a calculator display, not a code editor.

Raw HTML with v-html

<!-- v-html renders raw HTML. This is POWERFUL but DANGEROUS.
     If the content comes from users, attackers can inject
     <script> tags (XSS attack). Only use on trusted content. -->
<p v-html="rawHtml"></p>
Never use v-html with user-generated content. Attackers can inject malicious scripts. Only use it with trusted HTML from your own server.

Directives — HTML Superpowers

Directives are special HTML attributes starting with v-. They give HTML abilities it doesn’t normally have — like conditional rendering or looping. Think of them as superpowers for HTML.

v-bind — Connecting Data to Attributes

Normally, HTML attributes are static strings. With v-bind, you can make them dynamic — they react to your data:

<!-- v-bind binds an attribute to a JavaScript expression.
     Here, imgUrl comes from data(), and the src attribute
     updates whenever imgUrl changes. -->
<img v-bind:src="imageUrl" />

<!-- Shorthand: : instead of v-bind: -->
<a :href="link">Dynamic Link</a>

<!-- Dynamic classes: { active: isActive } means
     "add the 'active' class when isActive is true" -->
<div :class="{ active: isActive }">Styled</div>

<!-- Dynamic styles -->
<div :style="{ color: textColor, fontSize: size + 'px' }">Inline</div>

Conditional Rendering — v-if / v-show

<!-- v-if removes the element from the DOM entirely
     when the condition is false. It's like cutting paper with scissors. -->
<p v-if="score >= 90">Excellent</p>
<p v-else-if="score >= 70">Good</p>
<p v-else>Keep Trying</p>

<!-- v-show just hides with CSS display:none.
     The element is still in the DOM, just invisible.
     It's like putting something in a drawer vs. throwing it away. -->
<p v-show="isVisible">Always rendered, hidden with display:none</p>

v-if vs v-show comparison:

ScenarioUseWhy
Content rarely changesv-ifSaves DOM nodes, less memory
Content toggles frequentlyv-showFaster toggle, no DOM teardown/rebuild
Initial render not neededv-ifAvoids rendering hidden content
Visibility changes in msv-showCSS toggle is instant

v-for — List Rendering

<ul>
  <!-- :key is REQUIRED  helps Vue track items by identity, not position.
       Without :key, if you reorder the list, Vue might think
       "the first item's text changed" instead of "items moved positions."
       Always use a unique, stable ID. -->
  <li v-for="item in items" :key="item.id">
    {{ item.name }}
  </li>
</ul>

<!-- With index -->
<li v-for="(item, index) in items" :key="item.id">
  {{ index }}: {{ item.name }}
</li>

<!-- Iterating objects -->
<li v-for="(value, key, index) in obj" :key="key">
  {{ index }}. {{ key }}: {{ value }}
</li>

v-on — Event Handling

<button v-on:click="count++">+1</button>                  <!-- Inline expression -->
<button @click="handleSubmit">Submit</button>              <!-- Shorthand: @ -->
<button @click="say('Hello', $event)">Say Hello</button>  <!-- With argument -->

<!-- Event modifiers  chain after the event name with a dot:
     .prevent = preventDefault(), .stop = stopPropagation() -->
<form @submit.prevent="onSubmit">...</form>                <!-- No page reload -->
<a @click.stop="doThis">Click</a>                          <!-- No bubbling -->
<input @keyup.enter="submit" />                            <!-- Enter key only -->

v-model — Two-Way Binding

v-model creates a two-way connection: when the input changes, data updates. When data changes, the input updates. It’s like a two-way mirror between your data and the form:

<input v-model="username" placeholder="Username" />
<textarea v-model="bio"></textarea>
<input type="checkbox" v-model="agree" />
<input type="radio" v-model="picked" value="option-a" />
<select v-model="selected">
  <option disabled value="">Select</option>
  <option>A</option>
  <option>B</option>
</select>

<!-- Modifiers change how v-model behaves -->
<input v-model.lazy="name" />       <!-- Sync on change (blur), not keystroke -->
<input v-model.number="age" />      <!-- Convert string to number automatically -->
<input v-model.trim="email" />      <!-- Remove leading/trailing whitespace -->

v-model vs manual binding:

ApproachCodeBest For
v-model<input v-model="name">Quick forms, simple data
Manual<input :value="name" @input="name = $event.target.value">Custom logic on update, transformations

Computed Properties — The Formula Cells

Remember the spreadsheet analogy? Computed properties are Vue’s formula cells — they derive values from existing data and cache the result. A computed property only recalculates when its dependencies change, not every time the template re-renders:

<script>
export default {
  data() {
    return {
      todos: [
        { text: "Learn Vue", done: false },
        { text: "Build a project", done: true }
      ]
    };
  },
  computed: {
    // activeTodos is like =FILTER(todos, done=false).
    // It recalculates ONLY when todos changes.
    // The result is CACHED — huge performance win for expensive operations.
    activeTodos() {
      return this.todos.filter(t => !t.done);
    },
    todoCount() {
      return this.todos.length;
    }
  }
};
</script>

<template>
  <p>{{ activeTodos.length }} of {{ todoCount }} remaining</p>
</template>

Computed vs method: A method runs every time it’s called. A computed property caches its result until dependencies change. For filtering 10,000 items, computed is far more efficient — the method would re-filter on every render.

Watchers — Surveillance Cameras

Watchers let you react to data changes for side effects — things that don’t produce a value but need to happen when data changes, like API calls or saving to localStorage:

<script>
export default {
  data() {
    return {
      searchQuery: "",
      results: []
    };
  },
  watch: {
    // This "camera" watches searchQuery.
    // newVal is the new value after the change.
    // oldVal is the previous value before the change.
    searchQuery(newVal, oldVal) {
      // Only trigger API call if query is long enough
      // to avoid too many requests (debouncing).
      if (newVal.length > 2) {
        this.fetchResults(newVal);
      }
    }
  },
  methods: {
    fetchResults(query) {
      fetch(`/api/search?q=${query}`)
        .then(res => res.json())
        .then(data => this.results = data);
    }
  }
};
</script>

Watcher vs computed — when to use which:

NeedUseExample
Derive a valuecomputedFull name from first + last
React with side effectwatchSave to localStorage on change
Debounced API callwatchSearch autocomplete, form auto-save

Class & Style Bindings

<template>
  <!-- Object syntax: add/remove classes based on boolean flags -->
  <div :class="{ active: isActive, 'text-danger': hasError }">Content</div>

  <!-- Array syntax: combine multiple class variables -->
  <div :class="[baseClass, errorClass]">Content</div>

  <!-- Conditional class inside an array -->
  <div :class="[baseClass, isActive ? 'active' : '']">Content</div>

  <!-- Style binding: camelCase or quoted strings -->
  <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">Content</div>

  <!-- Merge multiple style objects -->
  <div :style="[baseStyles, overrides]">Content</div>
</template>

Lifecycle Hooks — Vue’s Timeline

Every Vue component goes through stages: create → mount → update → destroy. You can run code at each stage using lifecycle hooks:

    flowchart LR
  A[beforeCreate] --> B[created]
  B --> C[beforeMount]
  C --> D[mounted]
  D --> E[beforeUpdate]
  E --> F[updated]
  D --> G[beforeUnmount]
  G --> H[unmounted]

  style D fill:#42b883,color:#fff
  
<script>
export default {
  beforeCreate() { console.log("instance initialized — nothing reactive yet"); },
  created() { console.log("reactive data ready"); },
  beforeMount() { console.log("about to render to DOM"); },
  mounted() { console.log("DOM is in the page — safe to fetch data"); },
  beforeUpdate() { console.log("data changed, about to re-render"); },
  updated() { console.log("DOM re-rendered"); },
  beforeUnmount() { console.log("cleanup time"); },
  unmounted() { console.log("gone — remove timers and listeners"); }
};
</script>

Where to put common logic:

  • Fetch data: mounted() — the component is in the DOM
  • Set up timers: mounted(), clean up in unmounted()
  • Log page views: mounted() or route guard
  • Auto-save drafts: watch + debounce

Options API vs Composition API

FeatureOptions APIComposition API
Structuredata, methods, computed sectionsref(), functions, composables
Code organizationSplit by option typeSplit by logical concern
Reuse logicMixins (name conflicts possible)Composables (no conflicts)
TypeScript supportLimitedExcellent
When to useSimple components, legacy codeNew code, complex apps

This tutorial uses Options API — it’s more explicit for beginners. The advanced tutorial covers Composition API.

Common Mistakes

1. data must be a function, not an object

// ❌ Bad — all component instances share the same state
data: { count: 0 }

// ✅ Good — each instance gets its own copy
data() { return { count: 0 }; }

Why? If data were a plain object, clicking a button in one instance would change the count in ALL instances. The function creates a fresh copy per instance.

2. Missing :key in v-for

Without a unique :key, Vue may reuse elements incorrectly and cause state bugs when the list changes. Always use a stable, unique identifier.

3. Using v-if and v-for together on the same element

<!--  Bad: v-for runs first (higher priority), so v-if runs on every item.
     Vue shows a warning about this in development. -->
<li v-for="item in items" v-if="item.visible" :key="item.id">

<!--  Good: filter with a computed property instead -->
<li v-for="item in visibleItems" :key="item.id">

4. Mutating props directly

Props flow down from parent to child. Never modify a prop in the child — emit an event to notify the parent instead.

5. Overusing watchers

If you’re deriving a value (like fullName from firstName + lastName), use computed, not watch. Watchers are for side effects, not value derivation.

6. Forgetting .value in Composition API

In <script setup>, refs require .value inside <script> but NOT in templates. This trips up everyone at first.

Practice Questions

1. What does v-model do? Creates two-way data binding between a form input and a data property. Changes in either direction sync automatically.

2. What’s the difference between v-if and v-show? v-if adds/removes the DOM element. v-show toggles CSS display: none. Use v-if for rare changes, v-show for frequent toggles.

3. When should you use a computed property instead of a method? When the result depends on reactive data and you want caching. Computed properties only recalculate when dependencies change.

4. Which lifecycle hook is best for initial data fetching? mounted() — the component is in the DOM, and you can safely update reactive state with API results.

5. Why must data be a function in a component? So each instance gets its own copy. If data were a plain object, all instances would share the same state.

Challenge: Create a Vue app with a text input and a live character counter. Show “X characters remaining” (max 100). Turn the counter red when below 10. Disable input when limit is reached.

FAQ

What is the difference between v-if and v-show?
v-if removes/adds the element from the DOM; v-show toggles CSS display. Use v-if for infrequent changes, v-show for frequent toggling.
Can I use v-for and v-if together?
Not on the same element. v-for has higher priority than v-if. Use a computed property to filter the list first.
What is the data function in Vue?
A function that returns an object containing the component’s reactive state. Each component instance gets its own copy.
How do I handle form inputs in Vue?
Use v-model for two-way binding. It works on input, textarea, select, checkbox, and radio elements with modifiers.
What are computed properties used for?
Deriving values from existing reactive data with automatic caching. They only re-evaluate when their dependencies change.
What lifecycle hook should I use for API calls?
Use mounted() for fetching data. The component is in the DOM and you can safely update reactive state.

Try It Yourself

Copy this into a new HTML file and open it in your browser:

<!DOCTYPE html>
<html>
<head>
  <title>Vue Basics Sandbox</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
  <style>
    body { font-family: sans-serif; max-width: 500px; margin: 40px auto; padding: 0 20px; }
    .counter { display: flex; gap: 12px; align-items: center; margin: 20px 0; }
    .counter button { padding: 8px 16px; font-size: 18px; cursor: pointer; background: #42b883; color: white; border: none; border-radius: 4px; }
    .counter span { font-size: 24px; font-weight: bold; min-width: 40px; text-align: center; }
    .greeting { padding: 16px; background: #f0fdf4; border-radius: 8px; border: 1px solid #42b883; }
    .remaining { margin-top: 8px; font-size: 14px; }
    .remaining.warning { color: #e74c3c; font-weight: bold; }
  </style>
</head>
<body>
  <div id="app">
    <h1>Vue Counter</h1>
    <div class="counter">
      <button @click="count--">-</button>
      <span>{{ count }}</span>
      <button @click="count++">+</button>
    </div>
    <p>Double: {{ double }}</p>

    <hr>
    <h2>Live Greeting</h2>
    <input v-model="name" placeholder="Enter your name">
    <div class="greeting">
      Hello, <strong>{{ name || "Stranger" }}</strong>!
    </div>

    <hr>
    <h2>Character Limit</h2>
    <input v-model="text" maxlength="100" placeholder="Type something...">
    <p :class="{ remaining: true, warning: remaining < 10 }">
      {{ remaining }} characters remaining
    </p>
  </div>

  <script>
    const { createApp } = Vue;
    createApp({
      data() {
        return {
          count: 0,
          name: "",
          text: ""
        };
      },
      computed: {
        double() { return this.count * 2; },
        remaining() { return 100 - this.text.length; }
      }
    }).mount("#app");
  </script>
</body>
</html>

What to try: Click + and - to see reactivity in action. Type in the input — the greeting updates live. Watch the character counter decrease and turn red below 10.

What’s Next

ResourceDescription
https://tutorials.dodatech.com/frameworks/vue/vue-components/Learn components, props, slots, and emits
https://tutorials.dodatech.com/frameworks/vue/vue-router/Client-side routing with Vue Router
JavaScriptSolidify your JavaScript fundamentals
TypeScriptAdd type safety to your Vue apps

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

What’s Next

Congratulations on completing this Vue Basics tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro