This document explains how reconciliation works in our React-like implementation and the improvements made to fix common issues.
Reconciliation is the process of comparing the new virtual DOM tree with the previous one and determining what changes need to be made to the actual DOM. It's the core algorithm that makes React efficient by minimizing DOM manipulations.
We use a fiber-based architecture similar to React's implementation:
type FiberNode = {
type: string | undefined;
dom: Node | null;
parent: FiberNode | null;
child: FiberNode | null;
sibling: FiberNode | null;
props: PropsObject;
effectTag: "UPDATE" | "PLACEMENT" | "DELETION" | "";
alternate: FiberNode | null;
key?: string | number;
}- Interruptible: Uses
requestIdleCallbackfor time-sliced rendering - Side-effect free: Only creates fiber tree, doesn't modify DOM
- Reconciles children: Compares old and new elements to determine changes
- Synchronous: All DOM changes happen together
- Side-effects: Actually updates the DOM
- Handles: Placement, updates, and deletions
Our improved implementation supports key-based reconciliation for efficient list updates:
// Elements with keys are matched efficiently
const elements = [
{ type: 'div', key: 'item-1', props: {...} },
{ type: 'div', key: 'item-2', props: {...} }
];Benefits:
- Preserves component state when items reorder
- Avoids unnecessary DOM manipulations
- Better performance for dynamic lists
The reconcileChildren function:
- Creates fiber map: Maps old fibers by key for O(1) lookup
- First pass: Matches new elements with old fibers
- Key-based matching first (if keys exist)
- Positional matching for keyless elements
- Effect tagging:
UPDATE: Same type, update propsPLACEMENT: New element, insert into DOMDELETION: Element removed, delete from DOM
- Second pass: Mark unmatched old fibers for deletion
- PLACEMENT: New fiber needs to be inserted
- UPDATE: Existing fiber needs prop updates
- DELETION: Old fiber needs to be removed
- "" (empty): Root fiber, no DOM operation needed
- Before: Only positional matching, inefficient for lists
- After: Key-based matching with fallback to positional matching
- Impact: Better performance and state preservation in dynamic lists
- Before: Simple while loop with index, could miss edge cases
- After: Two-pass algorithm with proper old fiber tracking
- Impact: Handles all reconciliation scenarios correctly
- Before: Deletions only tracked in some cases
- After: All unmatched old fibers properly marked for deletion
- Impact: No memory leaks from orphaned fibers
- Before: Deletion array persisted across renders
- After: Deletions cleared after each commit
- Impact: Prevents memory buildup over time
- Before:
commitDeletiononly handled direct DOM nodes - After: Recursively handles all child deletions
- Impact: Properly cleans up complex component trees
- Before: Single commit per idle callback
- After: Multiple commits per callback when time allows
- Impact: Better utilization of idle time
// Initial render
const oldTree = [
{ type: 'div', key: 'a', props: { text: 'Hello' } },
{ type: 'div', key: 'b', props: { text: 'World' } }
];
// Update
const newTree = [
{ type: 'div', key: 'b', props: { text: 'World!' } }, // UPDATE (reordered + changed)
{ type: 'div', key: 'c', props: { text: 'New' } } // PLACEMENT (new)
];
// { key: 'a' } -> DELETION (removed)Result:
- Fiber 'a' marked for DELETION
- Fiber 'b' marked for UPDATE (moved position, changed props)
- Fiber 'c' marked for PLACEMENT (new element)
- Time Complexity: O(n) where n is number of elements
- Space Complexity: O(n) for fiber tree and reconciliation maps
- Key Lookup: O(1) with Map-based key matching
- Memory: Proper cleanup prevents leaks
While functional components aren't supported yet, the reconciliation layer is ready for:
- Component lifecycle methods
- Context propagation
- Suspense boundaries
- Concurrent features
The fiber architecture provides the foundation for these advanced React features.