React Virtual DOM and DOM Diff
Tags: react
Published: 2023-02-10 00:34:00

Virtual DOM works by using JS objects to simulate the nodes of the DOM. At the early stage of building React, Facebook introduced virtual DOM in consideration of factors such as improving code abstraction ability, avoiding artificial DOM operation and reducing the overall risk of code.
<br/>


### How it works?

The process likes that use **createElement** to build **virtual dom**, and it return a plain object that describes its tag type, props properties, children, and so on. These Plain objects form a virtual DOM tree through a tree structure.
&nbsp;
**render** function converts virtual dom into real dom, and mount real dom, like `document.getElementById("root").appendChild(realDom)`.
&nbsp;
When the status changes, There's **diff** functions to compare the difference of the virtual DOM tree before and after the change. Let's have a look below two graphs. There's four different places.
![A pie chart showing 40% responded "Yes", 50% responded "No" and 10% responded "Not sure"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/68gvprpyjodfb5g3v0ex.png)
![A pie chart showing 40% responded "Yes", 50% responded "No" and 10% responded "Not sure"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4uu8cx4q0h9ubyj69a6h.png)
The **diff** function returns an object, includes all different nodes information, we can call it **patches**.
&nbsp;
**doPatch** function bases on patches objects, will patch one by one on real dom.


<br/><br/>

#### Code snap examples 

##### How createElement looks like?

```
class Element {
	constructor(type, props, children) {
		this.type = type;
		this.props = props;
		this.children = children;
	}
}

function createElement(type, props, children) {
	return new Element(type, props, children);
}

```
More definitions about React.createElement {% embed https://beta.reactjs.org/reference/react/createElement %}

##### How virtual dom looks like?

```
const oldVDom = createElement(
	"ul",
	{
		class: "ul",
		style: "width: 400px; height: 400px; background-color: pink;",
	},
	[
		createElement(
			"li",
			{
				class: "li-wrap",
				"data-index": 0,
			},
			[
				createElement(
					"p",
					{
						class: "text",
					},
					["The first tag"]
				),
			]
		),
		createElement(
			"li",
			{
				class: "li-wrap",
				"data-index": 1,
			},
			[
				createElement(
					"p",
					{
						class: "text",
					},
					[
						createElement(
							"span",
							{
								class: "span",
							},
							["The second li tag, will be removed later on."]
						),
					]
				),
			]
		),
		createElement(
			"li",
			{
				class: "li-wrap",
				"data-index": 2,
			},
			["The third tag"]
		),
	]
);
```
##### How real dom looks like?

![A pie chart showing 40% responded "Yes", 50% responded "No" and 10% responded "Not sure"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9sfnbliqzh52lbbji14r.png)

##### How render function looks like?

```
function render(vDom) {
	// destructure virtual dom
	const { type, props, children } = vDom;
	// transform type
	const el = document.createElement(type);
	// transform all props
	for (let key in props) {
		// transform different attributes
		setAttrs(el, key, props[key]);
	}

	children.map((c) => {
		// recursive, if child is Element, then call render function again
		// break condition is if child is not Element
		c = c instanceof Element ? render(c) : document.createTextNode(c);
		el.appendChild(c);
	});

	return el;
}
```
##### How to mount real dom?

```
function renderDom(rDom, rootEl) {
	rootEl.appendChild(rDom);
}
renderDom(rDom, document.getElementById("root"));

```
##### How patches look like?

```
const patches = {
	0: [
		{
			type: "ATTR",
			attrs: {
				class: "ul-wrap",
			},
		},
	],
	3: [
		{
			type: "TEXT",
			text: "Update the first tag",
		},
	],
	6: [
		{
			type: "REMOVE",
			index: 6,
		},
	],
	7: [
		{
			type: "REPLACE",
			newNode: newNode,
		},
	],
};

```
##### How domDiff look like?

```
let patches = {};
let vnIndex = 0;

function domDiff(oldVDom, newVDom) {
	let index = 0;
	vNodeWalk(oldVDom, newVDom, index);
	return patches;
}

function vNodeWalk(oldNode, newNode, index) {
	let vnPatch = [];

	/**
	 * node can be
	 *  Element {type:'', props:{}, children: []}
	 *  text string
	 *  null
	 */
	if (!newNode) {
		// item was removed
		vnPatch.push({
			type: REMOVE,
			index,
		});
	} else if (typeof oldNode === "string" && typeof oldNode === "string") {
		// text change
		if (oldNode != newNode) {
			vnPatch.push({
				type: TEXT,
				text: newNode,
			});
		}
	} else if (oldNode.type === newNode.type) {
		const attrPatch = attrsWalk(oldNode.props, newNode.props);

		if (Object.keys(attrPatch).length > 0) {
			vnPatch.push({
				type: ATTR,
				attrs: attrPatch,
			});
		}

		childrenWalk(oldNode.children, newNode.children);
	} else {
		vnPatch.push({
			type: REPLACE,
			newNode,
		});
	}

	if (vnPatch.length > 0) {
		patches[index] = vnPatch;
	}
}

function childrenWalk(oldChildren, newChildren) {
	oldChildren.map((child, index) => {
		vNodeWalk(child, newChildren[index], ++vnIndex);
	});
}
```
##### How doPatch looks like?

```
let finalPatches = {};
let rnIndex = 0;

function doPatch(rDom, patches) {
	finalPatches = patches;
	rNodeWalk(rDom);
}

function rNodeWalk(rNode) {
	const rnPatch = finalPatches[rnIndex++];
	const childNodes = rNode.childNodes;

	[...childNodes].map((child) => {
		rNodeWalk(child);
	});

	if (rnPatch) {
		patchAction(rNode, rnPatch);
	}
}

function patchAction(rNode, rnPatch) {
	rnPatch.map((patch) => {
		switch (patch.type) {
			case ATTR:
				for (let key in patch.attrs) {
					const value = patch.attrs[key];
					if (value) {
						setAttrs(rNode, key, value);
					} else {
						rNode.removeAttribute(key);
					}
				}
				break;
			case TEXT:
				rNode.textContent = patch.text;
				break;
			case REPLACE:
				const newNode =
					patch.newNode instanceof Element
						? render(patch.newNode)
						: document.createTextNode(patch.newNode);
				rNode.parentNode.replaceChild(newNode, rNode);
				break;
			case REMOVE:
				rNode.parentNode.removeChild(rNode);
				break;
			default:
				break;
		}
	});
}
```