React · react-reconciler

Reconciliation

The algorithm itself, with no idea what a DOM is. It builds and diffs the fiber tree, calls your components, stores their hook state, and hands the resulting mutations to a host config you supply. It publishes as a package so you can write your own renderer.

01

Element

the description

What JSX compiles to: a plain object naming a type, a key and some props. It is not a component instance and it is not a DOM node — nothing has happened yet. Creating one is nearly free, which is what makes re-rendering a whole tree affordable.

{ $$typeof: Symbol(react.element),
  type: Row, key: '3', props: {…} }
02

Fiber

the unit of work

The mutable counterpart to an element: one node per component, holding its type, its host node, its hook state, and links to return, child and sibling instead of a children array — a linked list React can walk one node at a time.

fiber.return / .child / .sibling
fiber.memoizedState   // the hook list
03

The render phase

the interruptible half

beginWork walks down calling your components and reconciling their children, completeWork walks back up. It changes nothing outside React, so the loop can stop between any two fibers, let something more urgent through, and start over. That is why render must stay pure.

while (workInProgress && !shouldYield())
  performUnitOfWork()
04

Double buffering

the two trees

Each fiber keeps an alternate — its counterpart from the last commit. The next tree is built by cloning into those alternates, so the tree currently on screen stays whole and renderable the entire time. An abandoned render costs nothing to throw away.

wip = current.alternate ?? clone(current)
05

Keys & bailout

the diff

Matching children is deliberately shallow: same position and same type, reuse the fiber; different type, drop the subtree and rebuild it. A key overrides position, so a reordered list moves fibers rather than rebuilding them, and unchanged props let a subtree be skipped entirely.

items.map(i => <Row key={i.id} />)
06

Commit & lanes

the control

Commit is one synchronous pass over the flagged fibers — mutations applied, refs attached, layout effects run before paint, useEffect flushed after it. Which render gets to run at all is decided by lanes: a click is urgent, a startTransition update is not and can be abandoned mid-flight.

startTransition(() => setQuery(q))
// mutation → layout → paint → passive