fw-02 — Analysis
Required invariants
- Elements are plain data. Descriptions, not DOM nodes; the runtime decides what to do with them.
- Same type and key ⇒ reuse the DOM node. Node identity is what carries state.
- Identity must be author-supplied for lists. The runtime cannot derive it.
- Hook order is stable per component instance. Positional storage requires it.
- Effects clean up before the next run, and on unmount.
Why keys cannot be inferred
before: [A, B, C]
after: [B, C]
Deleted A? Or renamed A→B, B→C, deleted the third? Both are consistent with the data, and
they imply different DOM operations and different state outcomes. This is information-theoretic, not
an implementation limitation.
key={index} restores positional correspondence — it is not a weak key, it is no key with extra
steps, and it fails identically on reorder, prepend, and delete-from-middle.
The measurement that settles the virtual-DOM argument
The claim is not "diffing beats DOM mutation." It is:
build description + diff + minimal mutations < mutations a naive implementation performs
That inequality holds against innerHTML replacement and fails against "I know exactly which text
node changed." Which is why fine-grained reactive systems can win — they do know.
The spec measures the left-hand side directly: one change in a 200-item list must cost ≤5 DOM operations. A rebuild does ~200.
Failure modes
| Break | Consequence |
|---|---|
| Unkeyed reorder | state follows position, not identity — inputs keep the wrong values |
| Index as key | same bug, disguised |
| Conditional hook call | slot list shifts; every subsequent hook reads the wrong state |
| Missing effect dependency | stale closure reads a value from a previous render |
| Missing cleanup | subscriptions accumulate across remounts |
Every value in a component body is a snapshot of one render. Internalising that sentence
resolves most useEffect confusion, and it is a better model than "add the dependency."