Keentune

Vue curriculum

25 chapters
·
215 concepts
·
free
Everything the adaptive question bank can teach and test in Vue, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Vue guide
A free 18-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. The application instance and what an SFC is
createApp(Root) builds an app object; nothing renders until .mount() is called
.mount('#app') replaces the container's inner HTML and returns the root component instance
app.config.globalProperties and app.config.errorHandler apply to that app only, not to Vue itself
the runtime-only build cannot compile a string template; only the full build ships the compiler
an SFC is <template>, <script setup> and <style> compiled into one component module
B. Template syntax
{{ }} inserts escaped text and accepts exactly one expression
a binding holds an expression; if and var are syntax errors while a ternary is fine
v-html writes raw HTML, so it must never receive user input
a directive is v-name:arg.modifier="value", and only the value is evaluated as JavaScript
:[key]="v" computes the attribute name at runtime; it must resolve to a string or null
: is v-bind, @ is v-on, # is v-slot
binding null or undefined removes the attribute rather than printing the word
:id with no value binds the in-scope variable named id (3.4+)
expressions see only an allow-list of globals such as Math and Date; window is not exposed
C. Built-in directives: conditionals, lists, class and style
v-if creates and destroys, running child lifecycle hooks; v-show only toggles display
v-show does not work on <template> and has no v-else counterpart
v-else and v-else-if must be on the immediately following sibling element
v-for walks arrays, strings, plain objects as (value, key, index), and an integer range starting at 1
the key must be a stable primitive; using the index corrupts state when the list reorders
on one element Vue 3 evaluates v-if first, so it cannot see the v-for alias
<template v-for> repeats several nodes per item without introducing a wrapper element
mutating methods update in place; map/filter return a new array that must be assigned
:class takes a string, a condition object, or an array, and merges with the static class
:style accepts camelCase or kebab keys, auto-prefixes, and takes an array of values as fallbacks
v-once renders a subtree one time; v-pre tells the compiler to skip the subtree entirely
D. Reactivity fundamentals
ref() boxes any value; script code reads and writes .value
reactive() hands back a Proxy, so the proxy is not === the original object
reactive() ignores primitives; only objects, arrays, Map and Set become reactive
assigning a new object to a reactive variable abandons the proxy every dependency tracked
pulling a primitive out of a reactive object copies its current value and stops updating
a ref is auto-unwrapped in the template only when it is a top-level property of the render scope
a ref stored on a reactive object unwraps on read, and assignment writes through to .value
refs inside arrays, Map values or plain objects are not unwrapped; .value is required
reactive conversion is deep by default, converting nested objects as they are accessed
putting an object into a ref makes it deeply reactive through reactive()
several mutations in one tick produce one re-render; nextTick() waits for the flushed DOM
E. Computed properties
a computed re-evaluates only when a dependency changes; a method re-runs on every render
a getter must not mutate state, fetch, or touch the DOM
sort or reverse a copy, because the array methods mutate in place
the get/set form; the setter receives the assigned value and must write back to the sources
computed() yields a ref, so .value in script and auto-unwrapped in the template
reading a plain variable in a getter never invalidates the cache
the getter's first argument is the previously computed value (3.4+)
F. Event handling
a method name receives the native event; an inline statement needs $event or an arrow function
.stop, .prevent, .self, .capture, .once and .passive, applied in the order written
.self only checks that the target is this element; .stop halts propagation
@keyup.enter and kebab-cased key aliases, with .exact pinning the modifier combination
.ctrl, .alt, .shift and .meta require that key to be held while the event fires
.passive promises not to cancel, so combining it with .prevent is a mistake
G. Form input bindings
v-model expands to a value binding plus an input or change handler chosen by element type
a static value, checked or selected in the markup is ignored; the bound state wins
several checkboxes bound to one array collect their values into it
replaces the boolean a single checkbox writes
.lazy syncs on change, .number casts and falls back on NaN, .trim strips surrounding whitespace
v-model deliberately skips updates during IME composition; a raw @input listener does not
H. Lifecycle hooks and template refs
the <script setup> body runs before mount and has no this
the element tree exists at onMounted, not in setup
a hook registered after an await cannot bind to the current instance
timers, listeners and subscriptions leak unless torn down here
onBeforeUpdate and onUpdated fire per re-render, and mutating state inside onUpdated can loop forever
children mount before their parent, and unmount before it too
a template ref is null before mount and null again after unmount
a template ref on a repeated element collects an array whose order is not guaranteed
onErrorCaptured, plus the development-only onRenderTracked and onRenderTriggered
I. Watchers
a ref, a getter function, a reactive object, or an array of those
watching a reactive object watches it deeply, so old and new value are the same object
watch(obj.count, …) passes a plain number; wrap it as () => obj.count
deep: true traverses the whole tree on every change and can dominate the update
fires the callback once at creation with the current value
the watcher stops itself after the first callback (3.4+)
watchEffect runs immediately and subscribes to whatever it read during that run
dependencies accessed after an await inside the effect are not tracked
pre runs before the render, post after the DOM updates, sync on every change
the cleanup runs before the next invocation and on stop, which is how a stale request is cancelled
a watcher created in setup stops with the component; one created asynchronously must be stopped by hand
a watcher exists for side effects; derived state belongs in a computed
J. Components: registration, instances, dynamic components
app.component() registers everywhere and defeats tree-shaking; local registration does not
PascalCase works in SFCs, while in-DOM templates must use kebab-case
every occurrence gets its own state, which is why data in the Options API is a function
Vue 3 allows a fragment root, and that changes how attributes fall through
<component :is> accepts a component object, a registered name, or an HTML tag string
a component may render itself by name, and needs a condition that terminates
K. Props and attribute fallthrough
only declared props become props; anything else lands in the fallthrough attributes
declare camelCase, pass kebab-case in templates
a prop is read-only, and writing to it warns and is overwritten on the parent's next render
mutating an object prop's fields does not warn but still couples child to parent
object and array defaults must come from a function or every instance shares one value
a bare attribute becomes true and an absent one false, unless the declared type union changes the cast
custom validators and type checks warn in dev and are stripped from production
v-bind="obj" spreads each key as its own prop
destructured props stay reactive from 3.5; before that they must be kept as toRefs
undeclared attributes and listeners are applied to the single root element automatically
a fallthrough class merges with the child's own class instead of replacing it
turns the automatic fallthrough off so v-bind="$attrs" can target an inner element
with several root nodes Vue warns unless $attrs is bound explicitly
L. Component events and v-model
defineEmits documents the contract and removes that listener from $attrs
$emit('name', payload) and the parent handler receiving it as the first argument
declare camelCase and listen in kebab-case; unlike props, emitted names are not transformed for you
a component event reaches only the direct parent; anything further needs a re-emit or provide
an undeclared @click on a component attaches to its root element instead
v-model on a component is the modelValue prop plus the update:modelValue event
v-model:title maps to the title prop and the update:title event, and several can coexist
defineModel() returns a writable ref that declares both the prop and the event (3.4+)
a custom modifier arrives in modelModifiers and the child decides what it means
M. Slots
<slot> marks where the parent's content is rendered
slot content is compiled in the parent's scope and cannot read the child's state
markup inside <slot> shows only when the parent passed nothing
v-slot:name or #name on a <template>; leftover content becomes the default slot
the child binds props on <slot>, and the parent destructures them in v-slot="{ x }"
mixing a scoped default slot with named slots requires an explicit <template #default>
#[name] resolves which slot to fill at runtime
$slots.name is truthy only when the parent supplied it, so a wrapper can be skipped
a component that renders nothing but a scoped slot ships behavior without markup
N. Provide and inject
an ancestor provides a key and any descendant injects it, skipping the intermediate props
app.provide() makes a value visible to every component in that app
a Symbol key avoids name collisions and carries the type in TypeScript
the second argument is a default, and a third true marks it a factory
provide a ref or computed; a plain value never updates its consumers
provide an updater function so writes stay in the providing component
wrapping the provided ref in readonly() stops descendants from writing to it
O. Built-in components: Transition, KeepAlive, Teleport, Suspense
<Transition> animates one element or component entering and leaving
v-enter-from, v-enter-active, v-enter-to and the mirrored leave classes
name="fade" renames every class from the v- prefix to fade-
it reacts to v-if, v-show or a changing <component :is>, not to a data change alone
without a mode both elements exist at once and the layout jumps
TransitionGroup animates a list, requires keys, and adds the v-move class for repositioning
the JavaScript hooks, with :css="false" telling Vue to skip class detection
KeepAlive caches a toggled component's state and DOM instead of destroying it
cached components get onActivated and onDeactivated rather than mount and unmount
matching is by component name, and max evicts the least recently used
the node is relocated but props, provide and emitted events still follow the logical parent
renders in place instead, which is how a modal degrades on mobile
a loader returning a Promise, with optional loading and error components, delay and timeout
the bundle splits only when the loader is a real dynamic import
<Suspense> shows its fallback until every async dependency resolves, and is still experimental
P. Composables
a composable is an ordinary function that uses the reactivity APIs, named useX
calling it at the top of setup is what binds its lifecycle hooks and effect scope
each call creates fresh state; state declared at module scope is shared by every caller
return refs so the caller keeps reactivity after destructuring the result
normalize the argument with toValue() so a ref, a getter or a plain value all work
pair every listener or timer with an onUnmounted so repeated use does not leak
explicit sources and no silent key collisions, which is why mixins are discouraged in Vue 3
Q. Custom directives and plugins
created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted
the hook receives value, oldValue, arg, modifiers and the owning instance
a bare function is registered for the mounted and updated hooks
a variable named vFocus is usable as v-focus with no registration step
a directive applied to a component lands on its single root and warns on a fragment
a plugin is an object with install(app, options), or a function, applied via app.use()
R. Reactivity in depth and the advanced APIs
Vue 3 traps get and set on a Proxy, so added and deleted keys are reactive without a helper
the Vue 2 mechanism could not observe new properties or index assignment
a getter records the currently running effect; a setter re-runs the effects that recorded
a component's render function is itself an effect, which is why reading a ref subscribes that component
only replacing .value triggers; mutating inside the value does not
forces subscribers of a shallowRef to run after an in-place mutation
only root-level properties are reactive and nested objects stay raw
opting an object out of proxying, and recovering the original object from a proxy
takes explicit control of when tracking and triggering happen, as a debounced ref does
groups effects so one .stop() disposes watchers created outside a component
converting a reactive object into refs that survive destructuring, with toRef also wrapping a getter
a readonly proxy warns on write and is a different object from its source
S. The rendering mechanism and render functions
a render produces a virtual node tree that is patched against the previous tree
the compiler marks which bindings on a node can change, so the diff skips the static parts
nodes with no bindings are created once at module scope and reused across renders
dynamic descendants are collected in a flat array, making diff cost proportional to dynamic content
a structural directive opens a new block because the node shape can change
skips patching a subtree while every value in its dependency array is unchanged
changing a component's key destroys and recreates it rather than patching in place
h(type, props, children) builds a vnode, with children as a string, an array, or a slots object
in a render function slots are passed as functions so they can be re-invoked on update
templates earn the compiler's optimizations; render functions and JSX trade them for full JavaScript control
T. Single-file components, <script setup> and TypeScript
top-level bindings are exposed to the template without an explicit return
defineProps, defineEmits, defineExpose, defineOptions and defineModel are compiled away and are not imports
a macro's argument may reference imports but not other <script setup> locals
a <script setup> component is closed by default, so a template ref sees only what is exposed
permitted, and it turns the component into an async dependency of <Suspense>
a plain <script> alongside <script setup> runs once at module scope, for named exports
<style scoped> adds a data attribute, and a child's root element is still hit by the parent's scoped rules
:deep() reaches into a child, :slotted() targets slot content, :global() escapes scoping
v-bind() inside a style block compiles to a CSS custom property updated at runtime
a type-only defineProps<T>() replaces the runtime declaration, with withDefaults supplying defaults
generic="T" on <script setup> types props against a caller-supplied type parameter
U. Vue Router
a route record maps a path to a component that renders inside <RouterView>
createWebHistory needs a server catch-all rewrite; createWebHashHistory does not
/user/:id exposes route.params.id, with modifiers for optional and repeatable segments
navigating between two params of the same route reuses the component, so setup does not run again
child routes render into the parent component's own <RouterView>
global beforeEach, then per-route beforeEnter, then the in-component guards
returning false cancels the navigation, returning a location redirects, returning nothing continues
useRoute() is the reactive current location; useRouter() is the instance you navigate with
a dynamic import in the route record splits the bundle per route
V. State management with Pinia
defineStore(id, setup) returns a composable, and the id must be unique across the app
every useStore() call within one app returns the same store instance
calling a store before the Pinia plugin is installed throws
destructuring a store loses reactivity; storeToRefs keeps state and getters reactive while methods destructure freely
in a setup store, ref is state, computed is a getter, and a function is an action
$patch applies several changes as one entry for subscribers and devtools
W. Server-side rendering and hydration
the server produces HTML and the client attaches to that existing DOM instead of rebuilding it
if the client's first render disagrees with the server HTML, Vue warns and discards the markup
Date.now(), Math.random(), locale formatting and invalid HTML nesting corrected by the parser
defer browser-dependent markup behind an onMounted flag or a client-only wrapper
window and document do not exist on the server, so DOM access belongs in onMounted
module-scoped state is shared by every request; app and store must be created per request
only the creation hooks run; mount and update hooks never fire during a server render
server-fetched state is inlined into the page so the client does not refetch it during hydration
X. Performance, production builds and security
passing a freshly built object every render defeats the child's ability to skip updating
the fix for a very long list is rendering fewer nodes, not a faster diff
avoid deep-proxying a big structure that is replaced wholesale
the development build carries warnings and devtools hooks and must never be shipped
v-html, dynamic :href, dynamic :style and <component :is> are the documented injection points
compiling a user-supplied template runs arbitrary JavaScript
Y. The Options API
data is a factory so that each instance gets its own object
options methods bind this to the instance, which is why an arrow function in methods breaks
the watch option keys on a dotted property path and accepts a method name as the handler
bindings returned from setup() are reachable from options, but not the reverse
mixin options merge with the component winning conflicts, and the resulting opacity is why mixins were deprecated in favor of composables
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Vue practice
Also on your phone
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC