Skip to content
Lesson 2 of 14

Step 1 of 5 · Reading · ~3 min

Read

Reactive Foundations

Virtual DOM Trees

A virtual node (vnode) is a plain object that describes one piece of UI and holds no DOM. After this lesson your program builds vnodes the way a framework's factory function does, normalising everything callers throw at it, and prints them in a fixed form.

What a vnode holds

vnode
  type      "div"                   a tag name, or a component (a function)
  key       "a" or null             identity among siblings
  props     { id: "app", ... }      everything except key
  children  [ text | null | vnode ]

It is cheap on purpose: allocating and comparing plain objects costs nothing next to touching the real DOM, so a framework can throw the whole tree away and rebuild it on every render. The expensive DOM work happens only where two trees disagree (lesson 5). On the first render a walk over the tree creates real nodes: a text child becomes a text node, an element becomes createElement(type) with its props applied and its children appended; a function type is called with its props first and its result is walked instead.

Writing vnodes by hand is unbearable, so frameworks ship a factory (h in Preact, createElement in React) taking (type, props, ...children). JSX is sugar that a build step rewrites into calls to it (lesson 4). The factory does more than store its arguments. It normalises.

What the factory does to children

Caller passesThe child slot holds
a stringa text child with that string
a number, including 0a text child with its decimal form (42 becomes "42")
null, true, falsea null placeholder, rendered as nothing
an arrayits items, flattened into consecutive slots
a vnodethat vnode

Two details are worth stopping on.

Placeholders keep their slot. Take show && <Panel/> followed by <Footer/>. When show is false the first slot holds a placeholder, so Footer stays at index 1 either way. If false simply vanished, Footer would slide to index 0 on exactly the render where show flips, and a position-based comparison would pair it with the wrong node. Positions are identity until keys exist.

Zero is content. Only null, true and false are empty. count && <Badge/> with count equal to 0 evaluates to the number 0, which is a legitimate child, so the screen shows a stray 0. That is a classic bug; the factory is correct.

Props and key

Every attribute becomes a prop, in the order given, with one exception: key is not a prop. It is identity, not data, so it moves to the vnode's key field and the component never sees it (lesson 8 uses it). A bare attribute such as disabled means the value true.

The exercise format

You will read a script of calls and print the one root vnode on a single line:

OPEN div id=app
TEXT hello
FALSE
NUM 0
CLOSE
{"type":"div","key":null,"props":{"id":"app"},"children":["hello",null,"0"]}

OPEN type [attr ...] starts a call, CLOSE ends it, and everything between is its children: TEXT s, NUM n, NULL, TRUE, FALSE, a nested OPEN ... CLOSE, or LIST ... END, an array whose items are spliced into the parent. The exact output grammar is in the exercise text.

Common mistakes

  • Dropping false or null instead of keeping a null slot, which shifts every later sibling.
  • Treating 0 as empty and printing null for NUM 0.
  • Leaving key in props, so it prints twice.
  • Printing a number unquoted (42) instead of as the text child "42".
  • Nesting the array instead of flattening it, including arrays inside arrays.
  • Writing a bare attribute as "disabled":"" instead of true.

Your exercise

The starter reads the script lines. You write build, which turns them into the output string. Nothing needs a real tree if you are careful about commas, but you may build vnode objects first and print them after.

Up nextComponentsReactive Foundations

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…