liveBook · Manning

patchDOM

As explained in Build a Frontend Web Framework (From Scratch)

patchDOM is a book-specific function that compares virtual DOM trees and applies the required changes to a parent real DOM element.

When application state changes, replacing the entire DOM tree does more work than necessary. `patchDOM(oldVdom, newVdom, parentEl)` compares the previously rendered virtual DOM with the new one, applies changes beneath the parent element, and returns the new virtual DOM. Initial rendering uses `mountDOM()`; later renders use `patchDOM()` instead of destroying and remounting everything. section 7.3 section 8.0

The function handles a few cases in order:

  • If the top virtual nodes are not equal, find the old DOM node’s position, destroy its subtree, and mount the new subtree there. For a fragment whose `el` refers to the parent, a missing position becomes `null`, so mounting appends it. section 8.2.3
  • Otherwise, copy `oldVdom.el` to `newVdom.el` to retain the DOM reference. For text nodes, change `el.nodeValue` only if the text differs. section 8.2.4
  • For nodes with children, compare the old and new child arrays using `arraysDiffSequence()`. Flatten fragment children first; then mount additions, destroy removals, move nodes with `insertBefore()`, and recursively patch moved or unchanged children. section 8.2.6

A small update

Suppose a new `<span>` appears between two `<p>` elements. Mount the span at that position; the second paragraph shifts right as a result, without a separate move operation. If that paragraph’s classes and text also change, update those on its existing DOM node. section 7.2.2

Listing 8.7 Patching the DOM when the top nodes are different (patch-dom.js)
import { destroyDOM } from './destroy-dom'
import { mountDOM } from './mount-dom'
import { areNodesEqual } from './nodes-equal'

export function patchDOM(oldVdom, newVdom, parentEl) {
  if (!areNodesEqual(oldVdom, newVdom)) {
    const index = findIndexInParent(parentEl, oldVdom.el)      #1
    destroyDOM(oldVdom)                              #2
    mountDOM(newVdom, parentEl, index)            #3

    return newVdom
  }
}

function findIndexInParent(parentEl, el) {
  const index = Array.from(parentEl.childNodes).indexOf(el)
  if (index < 0) {
    return null
  }

  return index
}

The first implementation case shows `areNodesEqual()` deciding whether to replace a subtree and `patchDOM()` returning the newly mounted virtual node. section 8.2.3

This deliberately simple replacement rule can rebuild an unchanged subtree when its parent changes. In return, ordinary updates can keep existing elements: a TODO input, for example, need not be replaced on every keystroke, so it keeps focus. section 8.2.3 section 8.0

How Build a Frontend Web Framework (From Scratch) covers it

How deep it goes
Builds the function through subtree replacement, text updates, and child operations, while accepting subtree rebuilds when top nodes differ.
Examples
Uses an inserted `<span>`, updated paragraphs, and a TODO input that retains focus.
What it assumes
Builds on previously implemented virtual DOM mounting and array-diff helpers rather than teaching those routines from zero here.
What you can do after
Build it from scratch to patch changed DOM portions rather than replace the whole rendered tree on each state change.
How it connects
Connects virtual DOM reconciliation to `mountDOM()`, `destroyDOM()`, `areNodesEqual()`, and `arraysDiffSequence()`.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Build a Frontend Web Framework (From Scratch). The section links point to the parts of the book it draws on.