> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual DOM

> Understand Preact's Virtual DOM implementation and how it efficiently updates the real DOM

The Virtual DOM (VDOM) is a lightweight JavaScript representation of the actual DOM. Preact uses it to efficiently determine what changes need to be made to the real DOM.

## What is a VNode?

A VNode (Virtual Node) is Preact's internal representation of a DOM element or component. Here's how VNodes are created (`src/create-element.js:47`):

```javascript theme={null}
export function createVNode(type, props, key, ref, original) {
  const vnode = {
    type,
    props,
    key,
    ref,
    _children: NULL,
    _parent: NULL,
    _depth: 0,
    _dom: NULL,
    _component: NULL,
    constructor: UNDEFINED,
    _original: original == NULL ? ++vnodeId : original,
    _index: -1,
    _flags: 0
  };

  if (original == NULL && options.vnode != NULL) options.vnode(vnode);

  return vnode;
}
```

### VNode structure

Each VNode contains:

* `type` - The element type (string for DOM elements, function for components)
* `props` - The element's properties and attributes
* `key` - A unique identifier for efficient list diffing
* `ref` - A reference to the actual DOM node or component instance
* `_children` - Child VNodes
* `_dom` - The corresponding real DOM node
* `_component` - The component instance (for component VNodes)
* `_parent` - The parent VNode
* `_depth` - Depth in the VNode tree

<Note>
  Properties prefixed with `_` are internal to Preact and should not be accessed directly.
</Note>

## How the Virtual DOM works

Preact's Virtual DOM operates in three phases:

1. **Render** - Components return VNodes describing what the UI should look like
2. **Diff** - Preact compares the new VNode tree with the previous one
3. **Commit** - Only the differences are applied to the real DOM

```mermaid theme={null}
graph LR
    A[Component renders] --> B[New VNode tree]
    B --> C[Diff algorithm]
    D[Old VNode tree] --> C
    C --> E[Minimal DOM updates]
    E --> F[Real DOM]
```

## The diff algorithm

The diff algorithm is the heart of Preact's Virtual DOM. It's implemented in `src/diff/index.js:58`:

```javascript theme={null}
export function diff(
  parentDom,
  newVNode,
  oldVNode,
  globalContext,
  namespace,
  excessDomChildren,
  commitQueue,
  oldDom,
  isHydrating,
  refQueue,
  doc
) {
  let tmp,
    newType = newVNode.type;

  // When passing through createElement it assigns the object
  // constructor as undefined. This to prevent JSON-injection.
  if (newVNode.constructor !== UNDEFINED) return NULL;

  if ((tmp = options._diff)) tmp(newVNode);

  // ... diffing logic
}
```

### Diffing components

When diffing components, Preact determines whether to reuse an existing instance or create a new one (`src/diff/index.js:100`):

```javascript theme={null}
const isClassComponent =
  'prototype' in newType && newType.prototype.render;

// Get component and set it to `c`
if (oldVNode._component) {
  c = newVNode._component = oldVNode._component;
  if (c._bits & COMPONENT_PENDING_ERROR) {
    c._bits |= COMPONENT_PROCESSING_EXCEPTION;
  }
} else {
  // Instantiate the new component
  if (isClassComponent) {
    newVNode._component = c = new newType(newProps, componentContext);
  } else {
    newVNode._component = c = new BaseComponent(
      newProps,
      componentContext
    );
    c.constructor = newType;
    c.render = doRender;
  }
  // ...
}
```

<Info>
  Preact differentiates between class and functional components by checking if `prototype.render` exists.
</Info>

### Diffing children

Child diffing is optimized using keys and a skew-based algorithm (`src/diff/children.js:45`):

```javascript theme={null}
export function diffChildren(
  parentDom,
  renderResult,
  newParentVNode,
  oldParentVNode,
  globalContext,
  namespace,
  excessDomChildren,
  commitQueue,
  oldDom,
  isHydrating,
  refQueue,
  doc
) {
  let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;
  let newChildrenLength = renderResult.length;

  oldDom = constructNewChildrenArray(
    newParentVNode,
    renderResult,
    oldChildren,
    oldDom,
    newChildrenLength
  );

  for (i = 0; i < newChildrenLength; i++) {
    childVNode = newParentVNode._children[i];
    if (childVNode == NULL) continue;

    // Morph the old element into the new one
    diff(
      parentDom,
      childVNode,
      oldVNode,
      globalContext,
      namespace,
      excessDomChildren,
      commitQueue,
      oldDom,
      isHydrating,
      refQueue,
      doc
    );
  }
}
```

## Key-based reconciliation

Keys help Preact identify which items have changed, been added, or removed in lists:

<Tabs>
  <Tab title="Without keys">
    ```jsx theme={null}
    // Inefficient - Preact can't identify items
    todos.map(todo => (
      <li>{todo.text}</li>
    ))
    ```

    Without keys, Preact may re-render all items even if only one changed.
  </Tab>

  <Tab title="With keys">
    ```jsx theme={null}
    // Efficient - Preact tracks items by key
    todos.map(todo => (
      <li key={todo.id}>{todo.text}</li>
    ))
    ```

    With keys, Preact can reuse existing DOM nodes and only update what changed.
  </Tab>
</Tabs>

<Warning>
  Never use array indices as keys if the list can be reordered. This can cause incorrect updates and poor performance.
</Warning>

## Finding matching VNodes

Preact's diff algorithm uses a sophisticated matching strategy (`src/diff/children.js:400`):

```javascript theme={null}
function findMatchingIndex(
  childVNode,
  oldChildren,
  skewedIndex,
  remainingOldChildren
) {
  const key = childVNode.key;
  const type = childVNode.type;
  let oldVNode = oldChildren[skewedIndex];
  const matched = oldVNode != NULL && (oldVNode._flags & MATCHED) == 0;

  // Check if types and keys match
  if (
    (oldVNode === NULL && key == null) ||
    (matched && key == oldVNode.key && type == oldVNode.type)
  ) {
    return skewedIndex;
  } else if (shouldSearch) {
    // Search nearby positions
    let x = skewedIndex - 1;
    let y = skewedIndex + 1;
    while (x >= 0 || y < oldChildren.length) {
      const childIndex = x >= 0 ? x-- : y++;
      oldVNode = oldChildren[childIndex];
      if (
        oldVNode != NULL &&
        (oldVNode._flags & MATCHED) == 0 &&
        key == oldVNode.key &&
        type == oldVNode.type
      ) {
        return childIndex;
      }
    }
  }

  return -1;
}
```

This algorithm optimizes for common cases like insertions, deletions, and reorderings.

## Efficient updates

Preact's Virtual DOM provides several performance benefits:

### Batched updates

Multiple state changes are batched together into a single DOM update:

```jsx theme={null}
// These three setState calls result in one DOM update
this.setState({ count: 1 });
this.setState({ name: 'Alice' });
this.setState({ active: true });
```

### Minimal DOM manipulation

Only the parts of the DOM that actually changed are updated:

<CodeGroup>
  ```jsx Before theme={null}
  <div className="container">
    <h1>Count: 5</h1>
    <p>Last updated: 10:30 AM</p>
  </div>
  ```

  ```jsx After theme={null}
  <div className="container">
    <h1>Count: 6</h1>  {/* Only this text node updates */}
    <p>Last updated: 10:30 AM</p>
  </div>
  ```
</CodeGroup>

### Smart component updates

Components can skip unnecessary renders using `shouldComponentUpdate()`:

```jsx theme={null}
import { Component } from 'preact';

class Child extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    // Only re-render if the id changed
    return this.props.id !== nextProps.id;
  }

  render() {
    return <p>Child {this.props.id}</p>;
  }
}
```

## Committing changes

After diffing, Preact commits the changes to the real DOM (`src/diff/index.js:406`):

```javascript theme={null}
export function commitRoot(commitQueue, root, refQueue) {
  for (let i = 0; i < refQueue.length; i++) {
    applyRef(refQueue[i], refQueue[++i], refQueue[++i]);
  }

  if (options._commit) options._commit(root, commitQueue);

  commitQueue.some(c => {
    try {
      commitQueue = c._renderCallbacks;
      c._renderCallbacks = [];
      commitQueue.some(cb => {
        cb.call(c);
      });
    } catch (e) {
      options._catchError(e, c._vnode);
    }
  });
}
```

This phase:

1. Applies refs to DOM nodes
2. Invokes lifecycle callbacks (like `componentDidMount`)
3. Handles errors gracefully

<Info>
  The commit phase is synchronous and cannot be interrupted, ensuring the DOM is always in a consistent state.
</Info>
