Vue 01 - Fundamentals
Why Vue? I figured, what if I could just extract the part that I really liked about React and build something really lightweight without all the extra concepts involved? I was also curious as to how its internal implementation worked. I started this experiment just trying to replicate this minimal feature set, like declarative data binding. That was basically how Vue started.
-- Evan You
Vue.js is a progressive JavaScript framework for building user interfaces. It builds on standard HTML, CSS, and JavaScript with a component-based programming model.
Creating app
Vite-based project scaffolding (fast dev server with HMR).
npm create vue@latest
cd your_project
npm install
npm run dev
During project creation, select: TypeScript, Vue Router, Pinia, ESLint, Prettier.
In VS Code, install the recommended Vue extension - Vue (Official).
And Prettier - Code formatter.
Into the browser (chrome/edge), install vuejs-devtools.
Project structure
src/
├── assets/ # Static assets (CSS, images)
├── components/ # Reusable components
├── router/ # Vue Router configuration
│ └── index.ts
├── services/ # API service classes
├── stores/ # Pinia stores
├── types/ # TypeScript interfaces and types
├── views/ # Page-level components (matched by router)
├── App.vue # Root component
└── main.ts # Application entry point
Entry point
main.ts
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
index.html — Vite uses this as the HTML entry point. The #app div is where Vue mounts.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Single File Components (SFC)
Vue uses .vue files that encapsulate a component's logic, template, and styles in a single file. Each SFC has three sections:
<script setup lang="ts">
const name = "Dark Lord";
</script>
<template>
<div class="hello">Hello, {{ name }}!</div>
</template>
<style scoped>
.hello {
color: #f00;
}
</style>
<script setup lang="ts">— component logic (Composition API with TypeScript)<template>— HTML-based template (Vue 3 allows multiple root elements)<style scoped>— CSS scoped to this component only
Vue has two API styles: Options API and Composition API. Options API uses a configuration object with data(), methods, mounted() etc. We use Composition API exclusively — it is the official recommendation for building applications with Vue.
Reactivity - ref()
Reactive state is the core of Vue. When reactive data changes, the template re-renders automatically.
import { ref } from "vue";
const count = ref(0);
// Access/modify the value in script via .value
count.value++;
// In templates, .value is NOT needed — Vue unwraps it automatically
// {{ count }} renders as "1"
ref() takes an argument and returns it wrapped in a ref object with a .value property.
Typing refs — type is inferred from the initial value, but you can be explicit:
import type { Ref } from "vue";
// Both forms work:
const year: Ref<string | number> = ref("2020");
const year = ref<string | number>("2020");
reactive() also exists — it makes an object itself reactive without .value. However, ref() is recommended as the default because reactive() has limitations: you cannot reassign the whole object, and destructuring loses reactivity.
Computed properties
When you need a value that is derived from reactive state, use computed(). Computed properties are cached — they only re-evaluate when their reactive dependencies change.
import { computed } from "vue";
const firstName = ref("Darth");
const lastName = ref("Vader");
const fullName = computed(() => {
return firstName.value + " " + lastName.value;
});
// fullName.value === "Darth Vader"
Computed properties are getter-only by default. Do not mutate state inside a computed getter (keep them side-effect free).
Template syntax - bindings
Vue uses an HTML-based template syntax. Under the hood, templates are compiled into optimized JavaScript.
Text interpolation — double curly braces {{ }}
<span>Message: {{ msg }}</span>
Attribute binding — v-bind or shorthand :
<div :id="dynamicId"></div>
<a :href="url">Link</a>
<img :src="imageUrl" :alt="description" />
One-time rendering — v-once prevents re-renders
<span v-once>This will never change: {{ msg }}</span>
Raw HTML — v-html (use with caution, XSS risk)
<p>Rendered HTML: <span v-html="rawHtml"></span></p>
JavaScript expressions are supported in all bindings:
<span>{{ count + 1 }}</span>
<span>{{ ok ? "YES" : "NO" }}</span>
<div :id="'list-' + id"></div>
Conditional rendering - v-if, v-show
v-if conditionally renders elements. The element is completely removed from the DOM when the condition is false.
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else-if="type === 'C'">C</div>
<div v-else>Not A/B/C</div>
v-show only toggles the CSS display property — the element stays in the DOM. Use v-show for frequent toggling, v-if for conditions that rarely change.
<div v-show="isVisible">Always in DOM, toggled via CSS</div>
Rendering a list - v-for
v-for renders a list of items from an array. Always provide a :key attribute — Vue uses it to track element identity for efficient DOM updates.
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.message }}
</li>
</ul>
v-for can also iterate over object properties or a range of integers:
<!-- Object properties -->
<div v-for="(value, key) in myObject" :key="key">
{{ key }}: {{ value }}
</div>
<!-- Integer range (1 to 10) -->
<span v-for="n in 10" :key="n">{{ n }}</span>
DOM events
Bind to DOM events with v-on or shorthand @.
<button @click="increment">Click me</button>
<!-- Inline expression -->
<button @click="count++">+1</button>
<!-- Access the event object -->
<button @click="(event) => handleClick(event)">Submit</button>
Event modifiers
Modifiers are postfixes denoted by a dot. They handle common event patterns declaratively.
<!-- Stop propagation -->
<a @click.stop="doThis"></a>
<!-- Prevent default (e.g., form submission) -->
<form @submit.prevent="onSubmit"></form>
<!-- Modifiers can be chained -->
<a @click.stop.prevent="doThat"></a>
<!-- Only trigger if event.target is the element itself -->
<div @click.self="doThat">...</div>
Common modifiers: .stop, .prevent, .self, .capture, .once, .passive.
Form bindings - v-model
v-model creates two-way data binding on form elements. It is syntactic sugar for binding a value and listening to input events.
Manual way:
<input :value="text" @input="(event) => text = (event.target as HTMLInputElement).value" />
With v-model:
<input v-model="message" placeholder="edit me" />
<textarea v-model="bio"></textarea>
<select v-model="selected">
<option value="a">A</option>
<option value="b">B</option>
</select>
v-model automatically picks the correct way to update based on the input type.
Watchers
Use watch to run side effects when reactive state changes. Unlike computed properties, watchers can perform async operations.
import { ref, watch } from "vue";
const searchQuery = ref("");
watch(searchQuery, async (newValue, oldValue) => {
console.log(`Query changed from "${oldValue}" to "${newValue}"`);
// e.g., fetch search results
});
watch can observe: a ref, a computed ref, a reactive object, a getter function, or an array of multiple sources.
watchEffect runs a callback immediately and re-runs it whenever any reactive dependency inside it changes (no need to specify sources explicitly):
import { watchEffect } from "vue";
watchEffect(() => {
console.log(`Count is: ${count.value}`);
// Automatically tracks count as a dependency
});
Defence Preparation
Be prepared to explain topics like these:
- What is a Single File Component (SFC) in Vue, and what are its three sections? — An SFC is a
.vuefile that combines a component's logic, template, and styles in one file. The<script setup lang="ts">section contains the component's TypeScript/JavaScript logic using the Composition API. The<template>section contains the HTML markup with Vue directives. The<style scoped>section contains CSS that is scoped to the component so it does not leak to other elements. This co-location keeps related code together while maintaining separation of concerns. - What is
ref()in Vue, and why do you need.valuein script but not in templates? —ref()creates a reactive wrapper around a value. In JavaScript, primitives (strings, numbers, booleans) are passed by value, so Vue cannot track changes to them directly.ref()wraps the value in an object with a.valueproperty, which Vue can intercept. In<script>, you access the actual value through.value. In<template>, Vue automatically unwraps refs, so you write{{ count }}instead of{{ count.value }}. - What is the difference between a
computed()property and a regular function in a template? — Acomputed()property caches its result and only re-evaluates when its reactive dependencies change. A function called in the template re-executes on every render, even if its inputs haven't changed. For expensive calculations or frequently rendered components,computed()avoids redundant work. Usecomputed()for derived values (e.g., filtered lists, formatted strings); use methods for actions that should run every time (e.g., event handlers). - What is the difference between
v-ifandv-show? When would you use each? —v-ifconditionally adds or removes elements from the DOM. When the condition is false, the element and its child components are destroyed and do not exist in the DOM at all.v-showalways renders the element but togglesdisplay: nonein CSS. Usev-iffor conditions that rarely change (avoids rendering cost). Usev-showfor conditions that toggle frequently (avoids the cost of creating/destroying DOM elements). - Why does
v-forrequire a:keyattribute, and what happens if you use array indices as keys? — Vue uses the:keyto track each list item's identity across re-renders. Without a unique, stable key, Vue reuses DOM elements by position, which causes bugs when items are reordered, inserted, or deleted — input states, animations, and component state get mixed up. Using array indices as keys has the same problem: when an item is removed from the middle, every subsequent item shifts index, making Vue think they changed. - How does
v-modelwork under the hood? —v-modelis syntactic sugar for two-way data binding. On an<input>,v-model="query"expands to:value="query" @input="query = $event.target.value". It binds the value from the reactive variable to the input, and on every input event, updates the variable. For different input types (checkbox, select, radio),v-modeladapts — usingcheckedandchangeevents for checkboxes, for example. - What is the difference between
watch()andwatchEffect()? —watch()requires you to specify which reactive sources to observe. It gives you both the old and new values, only runs when the specified source changes, and is lazy by default (does not run immediately).watchEffect()automatically tracks all reactive dependencies used inside its callback, runs immediately on creation, and re-runs whenever any dependency changes. Usewatchwhen you need the old value or want to watch specific sources; usewatchEffectfor simpler side effects.