Template syntax and directives
Vue’s templates are compiled, not interpreted. Everything here becomes a render function at build time, which is why the compiler can optimise around directives in ways JSX cannot. See 19_compiler_and_rendering.md.
The core directives
| Directive | Does |
|---|---|
v-if / v-else-if / v-else |
conditionally create or destroy the element |
v-show |
always render; toggle display: none |
v-for |
render a list |
v-bind (:) |
bind an attribute or prop to an expression |
v-on (@) |
attach an event handler |
v-model |
two-way binding |
v-html |
set innerHTML |
v-once |
render once and never update |
v-memo |
skip updating a subtree unless dependencies change |
v-if versus v-show
v-if unmounts: the component is destroyed, its state is lost, and onUnmounted runs. v-show keeps it in the DOM with display: none, so state and scroll position survive and toggling is cheap.
Use v-if when the branch is rarely taken or expensive to keep alive; v-show when it toggles frequently, such as a tab or a dropdown. v-show also does not work on <template> and does not respect the element’s own display rules if you fight it with CSS.
v-for
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
The key requirement is the same story as React’s: it gives each node a stable identity so the patch algorithm can move rather than rebuild, preserving DOM state. Index keys corrupt state on insert, delete or reorder. See ../05_react/key_property.md — the reasoning transfers exactly.
Two Vue-specific points:
- Never put
v-ifandv-foron the same element.v-ifhas higher priority in Vue 3, so it evaluates before the loop variable exists — you get an error rather than the filtering you wanted. Filter in acomputed, or wrap in a<template v-for>with thev-ifinside. v-forover an object iterates(value, key, index); over a number it iterates 1..n, which is occasionally handy for pagination controls.
Modifiers
Modifiers are the part that has no React equivalent, and interviewers use them to check whether you have actually written Vue.
<form @submit.prevent="save">
<input v-model.trim.lazy="name" @keyup.enter="save" />
<div @click.self="close" @scroll.passive="onScroll">
| Modifier | Effect |
|---|---|
.prevent / .stop |
preventDefault() / stopPropagation() |
.self |
fire only when the target is this element, not a child |
.once |
remove the listener after the first call |
.capture / .passive |
listener options; .passive is the scroll-performance one |
.enter, .esc, .ctrl |
key and system-modifier filters |
v-model.number / .trim / .lazy |
coerce to number, trim whitespace, sync on change instead of input |
v-model.number matters more than it looks: an <input type="number"> still produces a string, so a numeric comparison downstream fails silently without it.
v-html and security
v-html sets innerHTML and executes nothing that Vue sanitises — because Vue does not sanitise. Rendering user-supplied HTML through it is a direct XSS vector. Sanitise server-side, or with DOMPurify, or do not use it. Interpolation ({{ }}) is always escaped and is safe.
Dynamic attribute and event names (:[key]="v", @[evt]="h") built from user input are the same class of problem.
Custom directives
For low-level DOM access that a component cannot express: focus management, intersection observers, click-outside, tooltips attached to arbitrary elements.
const vFocus = {
mounted: (el) => el.focus(),
};
// in <script setup>, a `vFoo` const is usable as v-foo in the template
Hooks are created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted. The rule of thumb: reach for a component first, and a directive only when the behaviour must attach to an element you do not own. See 12_advanced_features.md.
Interview angle
- “
v-iforv-show?” -v-ifdestroys and recreates, so state is lost and initial cost is avoided;v-showonly toggles CSS, so toggling is cheap and state survives. Frequent toggles favourv-show; rarely-shown expensive content favoursv-if. - “Why can’t you use
v-ifandv-fortogether?” - in Vue 3,v-ifhas the higher priority and runs before the loop binding exists, so it cannot see the item. Filter in a computed property instead, which is also faster because the filtering happens once rather than per item per render. - “Why does
v-forneed a key?” - stable identity for the patch algorithm, so nodes move instead of being rebuilt and DOM state stays attached to the right item. Index keys break on insert, delete and reorder. - “What is the risk with
v-html?” - XSS. Vue escapes interpolation butv-htmlis rawinnerHTML. Never point it at user input without sanitising. - “When would you write a custom directive?” - when behaviour must attach to an arbitrary element rather than live inside a component: autofocus, click-outside, lazy-loading images, tooltip positioning.