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 passes | The child slot holds |
|---|---|
| a string | a text child with that string |
a number, including 0 | a text child with its decimal form (42 becomes "42") |
null, true, false | a null placeholder, rendered as nothing |
| an array | its items, flattened into consecutive slots |
| a vnode | that 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
falseornullinstead of keeping anullslot, which shifts every later sibling. - Treating
0as empty and printingnullforNUM 0. - Leaving
keyin 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 oftrue.
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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…