Patching updates existing browser DOM nodes after application state changes. Rather than destroy and rebuild the whole view, `patchDOM(oldVdom, newVdom, parentEl)` compares the previously rendered virtual tree with the new one and applies the necessary changes. This is the second part of reconciliation: diffing finds differences; patching applies them to the real DOM. section 7.3 section 7.7
Mounting creates the initial DOM once. Later state changes call `renderApp()`, which obtains a new virtual tree from `view()` and passes both trees to `patchDOM()`. That separation lets unchanged DOM nodes stay in place. section 7.3 section 8.0
Build the patcher one case at a time:
- Compare corresponding virtual nodes, starting at the top and continuing recursively through their children. Nodes of different types cannot share a DOM node; element nodes can share one when their tag names match. section 8.5
- When nodes cannot be reused, find the old node’s position, destroy its DOM subtree, and mount the new subtree at that position. Replacing a changed parent’s subtree keeps the algorithm simple, even when some descendants might have matched. section 8.2.3
- When nodes can be reused, copy the old virtual node’s DOM reference to the new virtual node. For text, change the DOM node’s `nodeValue` only if its value changed. section 8.2.4
- For elements, patch attributes, CSS classes, styles, and event listeners separately. For element or fragment children, use `arraysDiffSequence()` to determine which child operations to apply. section 8.5
Insert an error without replacing the form
Suppose validation adds an error paragraph between a form’s input and button. Mounting only at the end would put it in the wrong place, so `mountDOM()` needs an index. The new paragraph can then be inserted at child position 1. section 8.1
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 initial `patchDOM()` case shows the replacement path: find the old node’s index, destroy its subtree, and mount the new subtree there. section 8.2.3
The result is an update assembled from small DOM operations rather than a whole-tree replacement. In the TODOs application, input fields no longer lose focus on every keystroke because they are updated without being replaced. section 8.0 section 8.5
