Vue docs, chapter by chapter

Extra topics — internals and the edges

Ways of Using Vue, the Composition API FAQ, Reactivity in Depth, the Rendering Mechanism, Render Functions and JSX, Vue and Web Components, and Animation Techniques, as the guide presents them.

The Extra Topics are where the guide explains itself: why the Composition API exists, how reactivity and rendering actually work, and how Vue relates to render functions, web components and animation. Senior interviews live here.

Know — Ways of Using Vue

  • Standalone script (with the smaller petite-vue for progressive enhancement), embedded web components, single-page application, fullstack / SSR (Nuxt), JAMStack / SSG (VitePress), and beyond the web (Electron, Tauri, Ionic, Quasar, NativeScript, WebGL renderers).

Know — Composition API FAQ

  • Why: better logic reuse than mixins, more flexible code organisation (by feature), better type inference, smaller production bundle (tree-shakable, <script setup> compiles to less code).
  • Relationship: the Options API is implemented on top of the Composition API; both are supported, Options API is not deprecated, and they can be mixed (setup() option). The class API was dropped.
  • Versus React hooks: setup() runs once, so no stale closures, no dependency arrays, no useCallback/useMemo for stability, and no call-order rules; watchers and computed track dependencies automatically.

Know — Reactivity in Depth

  • Mechanism: track() on read records the running effect in a dependency set; trigger() on write re-runs the effects; whenDepsChange (an effect) wraps the update. Vue does this at runtime via Proxies, as opposed to compile-time reactivity (Svelte).
  • Debugging (dev only): onRenderTracked/onRenderTriggered on components; onTrack/onTrigger options on computed and watch.
  • External state systems: immutable data via shallowRef (the useImmer example), state machines (XState), RxJS (@vueuse/rxjs).
  • Signals: Vue's ref is a signal, computed a derived value, watchEffect an effect; the .value API exists because a getter/setter pair is needed to track access.

Know — Rendering Mechanism

  • Virtual DOM: vnodes describe the UI; mount creates real DOM, patch diffs two trees and applies minimal updates. Pipeline: compile (template → render function) → mount → patch on state change.
  • Templates vs render functions: templates are easier to read and, crucially, analysable by the compiler; render functions are for highly dynamic logic.
  • Compiler-informed virtual DOM: static hoisting (static vnodes created once, large static trees stringified), patch flags (TEXT, CLASS, PROPS… tell the runtime what can change), tree flattening (block nodes track only dynamic descendants; v-if/v-for open new blocks), and hydration skips static content using the same information.

Know — Render Functions and JSX

  • h(type, props?, children?); props can be omitted when children are given; children are a string, an array, or a slots object of functions. Vnodes must be unique: do not put the same vnode instance twice in a tree; use a factory.
  • Declaring: setup() returns a function, or the render option. JSX/TSX via @vitejs/plugin-vue-jsx (or jsxImportSource: 'vue').
  • Recipes: v-if → ternary, v-for → map, v-on → onClick, modifiers via withModifiers(fn, ['self']) or onClickCapture/onClickOnce; components are imported and passed as type, or resolveComponent('Name') for globally registered ones; slots: render with slots.default?.(), pass as an object of functions (scoped slots receive props); built-ins must be imported (h(KeepAlive, …)); v-model → modelValue plus 'onUpdate:modelValue'; custom directives via withDirectives(h(...), [[vPin, value, arg, modifiers]]) and resolveDirective; template refs via ref (object or function).
  • Functional components: plain functions (props, { slots, emit, attrs }) => vnode with props/emits set as static properties, inheritAttrs supported; typed with FunctionalComponent<Props, Emits, Slots>.

Know — Vue and Web Components

  • Using custom elements in Vue: tell the compiler with compilerOptions.isCustomElement: tag => tag.includes('-') (build-time, in the Vite plugin options); force a native element to be treated as a component with is="vue:my-component"; pass complex data as DOM properties with the .prop modifier (:value.prop or .value).
  • Building with Vue: defineCustomElement(Comp) returns a class for customElements.define('my-el', …); shadowRoot: false (3.5) renders in the light DOM; props are reflected from attributes (kebab → camelCase, Number/Boolean cast) and exposed as DOM properties; events are dispatched as CustomEvent with detail; slots are native (no scoped slots); provide/inject works only between custom elements; configureApp (3.5) installs plugins into the internal app; .ce.vue SFCs inline their CSS into the shadow root; useHost()/useShadowRoot() (3.5) and this.$host give access to the host element.
  • Library tips: export a register() that defines all elements; ship types via GlobalComponents augmentation. The guide's comparison: web components lack a data-flow model, Vue components need Vue.

Know — Animation Techniques

  • Class-based (toggle a class that carries a CSS animation), state-driven (bind :style to reactive values and let CSS transition interpolate), and watchers that tween numbers with a library such as GSAP for animated counters.

How it works

A functional component with a scoped slot and a directive, in h():

app/components/Docs/Repeat.ts
import { h, withDirectives, type FunctionalComponent } from 'vue'
import { vFocus } from './directives'

interface Props { times: number }
type Slots = { default: (props: { index: number }) => any }

const Repeat: FunctionalComponent<Props, {}, Slots> = (props, { slots }) =>
  h('ul', Array.from({ length: props.times }, (_, index) =>
    withDirectives(h('li', { key: index }, slots.default?.({ index })), [[vFocus, index === 0]]),
  ))

Repeat.props = { times: { type: Number, required: true } }
export default Repeat

A Vue component shipped as a custom element:

lib/elements.ts
import { defineCustomElement } from 'vue'
import Rating from './Rating.ce.vue' // .ce.vue inlines the styles into the shadow root

export const RatingElement = defineCustomElement(Rating, {
  configureApp(app) { app.provide('theme', 'dark') }, // 3.5+
})

export function register() {
  customElements.define('team-rating', RatingElement)
}
Gotcha· Reusing a vnode

const p = h('p', 'hi'); return h('div', [p, p]) is invalid: the same vnode instance cannot appear twice. The guide says to use a factory (Array.from({ length: 2 }, () => h('p', 'hi'))). Templates never hit this because the compiler generates fresh vnodes.

Exercise

Exercise
  • Compile a template with v-if, v-for and static text in the SFC Playground and read the generated render function; find the patch flags, the hoisted nodes and the block calls.
  • Wrap a small component with defineCustomElement, use it in a plain HTML page, and pass an object through a .prop binding from a Vue parent.
  • Write the same list component as a template and as a render function; measure update time with onRenderTriggered counts.

Be able to say

Be able to say· How does Vue's compiler make the virtual DOM faster than a hand-written render function?

"Because it knows the template's structure ahead of time. It hoists static nodes so they are created once, stringifies large static trees, attaches patch flags to dynamic nodes so the runtime checks only the bindings that can change, and flattens the tree into blocks that track only dynamic descendants, so a stable structure is skipped wholesale during a patch. Hydration uses the same information to skip static content. A hand-written h() tree has none of these hints, so every node is fully diffed; that is why the guide recommends templates unless the structure is genuinely dynamic."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.