skip to content

In Vue 3, what is a vnode, and what is the difference between the renderer mounting it and patching it?

level: juniorimportance: should knowfreq 55%

answer

  1. plain object, not a DOM node
  2. type, props, children, key
  3. first render creates, later renders compare
  4. same type and key: reuse el

basics

~20 s

A vnode is a plain JavaScript object describing a piece of UI: its type, props, children and key. Mounting creates real DOM from a new vnode tree; patching compares the new tree with the previous one and changes only what differs.

solid answer

~50 s

A Vue 3 template compiles to a render function that returns a tree of **vnodes** — plain objects with a `type` (tag, component or special symbol), `props`, `children` and `key`. On the first render the renderer **mounts** that tree: it creates each element, sets its attributes and listeners, inserts it, and stores the DOM node on `vnode.el`. The render runs inside the component's reactive render effect, so when state it read changes, the effect re-runs and returns a fresh tree. The renderer then **patches**: for each old/new pair with the same `type` and `key` it reuses the existing `el` and updates only changed props, text and children; when type or key differ it unmounts the old subtree and mounts the new one. Both go through one internal `patch(n1, n2)`, where a null `n1` means mount.

go deeper

for a junior

Recall that a vnode is a plain object describing UI, and name mount (create DOM) versus patch (compare and update existing DOM).

for a middle

Explain that patch reuses the DOM node only when type and key match, and that the render effect re-running is what produces the new tree.

for a senior

Connect mount versus patch to what survives: a patched component keeps its setup state, a replaced one loses it, which explains many lost-state bugs.

for a principal

Frame the virtual tree as the contract that lets templates and render functions share one renderer, and where compiler hints reduce the cost of that contract.

## What a vnode is A **vnode** (virtual node) is an ordinary JavaScript object that *describes* something Vue should render. It is not a DOM node and it is cheap to create and throw away. The fields that matter for rendering are: - `type` — a tag name such as `'div'`, a component definition object, or an internal symbol such as `Text`, `Comment` or `Fragment`. - `props` — attributes, DOM properties, `class`, `style` and event listeners such as `onClick`. - `children` — a string for text, an array of child vnodes, or a slots object for a component. - `key` — an optional identity used when comparing lists and deciding whether two vnodes are the same thing. - `el` — filled in by the renderer with the real DOM node once the vnode is mounted. - `component` — for a component vnode, the live component instance it produced. A tree of vnodes is what people call the **virtual DOM**. In Vue 3 you usually never build it by hand: the template compiler turns `<template>` into a render function, and that function returns the tree each time it runs. ```ts // roughly what one vnode looks like (simplified) const vnode = { type: 'button', props: { class: 'primary', onClick: save }, children: 'Save', key: null, el: null // set by the renderer after mount } ``` ## Mount: turning a description into DOM The first time a component renders, there is no previous tree to compare with, so the renderer **mounts**: 1. It calls the component's render function inside the component's **render effect**, which records every reactive value read while rendering. 2. It walks the returned tree top-down. For an element vnode it creates the element, applies props, mounts the children, and inserts the element into the container. 3. For a component vnode it creates a component instance, runs `setup()` once, and then mounts that component's own render output the same way. 4. It stores each created DOM node on its vnode's `el`, and keeps the whole tree as the component's current sub-tree so the next render has something to compare against. ## Patch: updating DOM that already exists When a value the render read changes, the render effect re-runs and produces a **new** vnode tree. The renderer now **patches** old against new: - If the old and new vnode have the same `type` and the same `key`, the renderer keeps the existing DOM node (`n2.el = n1.el`) and only updates what changed — a class, a text node, a listener. - If the `type` or `key` differs, patching stops for that node: the old subtree is unmounted (components inside it are destroyed) and the new one is mounted in its place. - Children are compared as text, as an array, or as empty, and arrays go through Vue's children-diffing algorithms. - A component vnode is not re-rendered blindly: the renderer checks whether its props or slots changed and skips it when they did not. Inside the runtime both operations share one function, `patch(n1, n2)`. When `n1` is `null` there is nothing to compare, so it mounts; otherwise it compares and updates. ## Mount and patch side by side | | Mount | Patch | |---|---|---| | When | First render of a vnode, or after a replacement | Every later render of the same vnode | | Input | New tree only | Previous tree and new tree | | DOM work | Create and insert every node | Reuse nodes, apply the differences | | Components | Create instance, run `setup()` once | Reuse instance, re-render only if needed | | Cost driver | Size of the tree | Size of what changed, plus comparison work | ## Why Vue bothers with a virtual tree The render function can be written declaratively — it always returns *the whole* description of what the UI should look like — while the renderer handles the imperative DOM calls. Vue's compiler adds hints to the tree so patching can skip static parts, but the mount/patch contract stays the same whether the render function came from a template or from hand-written `h()` calls. ## Common misunderstandings - A vnode is **not** a lightweight DOM element held in memory; it is a description, and a new tree is created on each render. - Patching is **not** limited to text: props, listeners, children order and whole subtrees can change. - A component's `setup()` does **not** re-run on patch; only its render function does. - Replacing a subtree is **not** the same as patching it: replacement discards DOM and component state.

  • In Vue 3, what happens to a component instance when its vnode is patched rather than replaced?
    The same instance is kept: its `setup()` state, refs and watchers survive. The renderer hands it the new vnode, updates its props and slots, and re-runs only its render function if something it depends on changed. Replacement, by contrast, unmounts the instance, runs its unmount hooks and creates a fresh one with a new `setup()` call.
  • Where does the real DOM node for a vnode live after mount, and why does patching need it?
    The renderer stores it on `vnode.el`. When the next render produces a new vnode of the same type and key, the renderer copies `el` across to the new vnode and applies changes to that node directly. Without the stored reference it would have to find or recreate the element.

saying these in an interview costs you the question

  • Says a vnode is a real DOM node kept detached in memory.
  • Believes Vue rebuilds the whole component DOM on every change.
  • Thinks setup() runs again each time the component patches.
  • Claims patching only updates text content, never attributes or children.
  • Treats replacing a subtree and patching it as the same operation.