-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
309 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"liveServer.settings.port": 5501 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
<div align=center> | ||
|
||
# Virtual Dom | ||
|
||
[Summary](#1-summary)</br> | ||
[How it work](#2-how-it-works)</br> | ||
[Code Implementation](#3-code-implementation)</br> | ||
[~ Last](#4--last)</br> | ||
|
||
</div> | ||
|
||
## 1. Summary | ||
This is a simplified virtual DOM implemented using native JavaScript. It includes key features like creating virtual nodes, rendering the virtual DOM to real DOM, performing a diff operation, and updating the real DOM. By recursively comparing differences between old and new virtual DOM trees, it generates update instructions (patches) and applies changes to the real DOM. This basic implementation demonstrates the core principles of a virtual DOM, though real-world applications may require further optimization and complexity adjustments. | ||
|
||
## 2. How it works | ||
This project contains just two files: a basic [HTML file](./index.html) and a [JavaScript file](./index.js). These files work together to create and update a virtual DOM structure, which is then used to render elements to the actual DOM. Here is a guide on the key functions and their usage: | ||
|
||
`createElement()` | ||
```javascript | ||
const dom = createElement('tagname', { | ||
attribute1: "value1", | ||
attribute2: "value2" | ||
}, ["Content or tag"]); | ||
``` | ||
> The `createElement` function generates virtual DOM nodes. You can define the tag type, attributes, and either nested elements or text content as children. This virtual node structure allows elements to be represented in memory before rendering them to the actual DOM. | ||
`renderElement()` | ||
```javascript | ||
parent.appendChild(renderElement(dom)); | ||
``` | ||
> The `renderElement` function takes a virtual DOM node and converts it into a real DOM element. After calling `createElement`, use `renderElement` to render the virtual node onto the page by appending it to a parent node in the actual DOM. | ||
`diff()` | ||
```javascript | ||
const patch = diff(oldDom, newDom); | ||
``` | ||
> The `diff` function compares two virtual DOM trees—`oldDom` and `newDom`—and identifies differences between them. This function generates a set of changes (patches) that can be applied to the actual DOM to make it match the new virtual DOM structure. | ||
`patch()` | ||
```javascript | ||
patch(parent, patch); | ||
``` | ||
> The `patch` function applies the changes generated by `diff` to the real DOM. Using the patch instructions, this function efficiently updates only the parts of the DOM that need changes, minimizing unnecessary re-renders and optimizing performance. | ||
Together, these functions create a streamlined process for building and updating a DOM structure with virtual DOM principles. By first creating a virtual DOM, rendering it, and then applying updates only when differences are detected, this project demonstrates a simplified virtual DOM setup. This structure offers a foundation for understanding virtual DOM operations. | ||
|
||
## 3. Code Implementation | ||
### 3.1 Create Element | ||
This function creates a virtual DOM node by taking in three arguments: tag, props, and children. It returns an object that represents the node: | ||
|
||
`tag` | ||
- The type of HTML tag (e.g., 'div', 'span'). | ||
|
||
`props` | ||
- An object containing the node’s attributes or properties, such as id or class. | ||
|
||
|
||
`children` | ||
- An array representing child nodes, which can be strings or other virtual DOM nodes. | ||
The function returns an object representing the virtual node structure, which is used for further rendering or updating. | ||
|
||
### 3.2 Render Element | ||
The renderElement function converts a virtual DOM node into an actual DOM node. | ||
|
||
- If the virtual node is a string, it creates and returns a text node. | ||
- If the virtual node is an object (created by createElement), it: | ||
1. Creates a DOM element based on the tag. | ||
2. Loops through each property in props to add them as attributes to the created element. | ||
3. Recursively renders each child in the children array and appends them as child nodes of the newly created element. | ||
- This function returns a fully constructed DOM element tree that mirrors the virtual DOM structure. | ||
|
||
### 3.3 Diff | ||
The diff function compares an old virtual DOM node with a new one and generates a list of patches (changes) required to transform the old DOM into the new one: | ||
|
||
1. Add or Remove Nodes | ||
> If oldNode or newNode is undefined, it creates an "ADD" or "REMOVE" patch. | ||
2. Text Changes | ||
> If both nodes are text nodes and their content differs, it generates a "TEXT" patch. | ||
3. Node Replacement | ||
> If the tag values of oldNode and newNode differ, it generates a "REPLACE" patch. | ||
4. Props Changes | ||
> Compares props in both nodes to identify differences, generating a "PROPS" patch containing any added, changed, or removed properties. | ||
5. Children Changes | ||
> Recursively compares child nodes in children, generating a "CHILDREN" patch with a list of child patches. | ||
The function returns an array of patches to update the DOM structure efficiently. | ||
|
||
### 3.4 Patch | ||
The patch function applies the patches generated by the diff function to update the real DOM: | ||
|
||
- It targets a DOM element (el) at a specified index in the parent node. | ||
- Each type of patch is handled by specific actions: | ||
1. **ADD**: | ||
- *Appends a new DOM node rendered from newNode to the parent.* | ||
2. **REMOVE**: | ||
- *Removes the target DOM element from the parent.* | ||
3. **TEXT**: | ||
- *Updates the text content of el with the new text.* | ||
4. **REPLACE**: | ||
- *Replaces el with a new DOM node created from newNode.* | ||
5. **PROPS**: | ||
- *Updates attributes of el by setting or removing properties as per the patch.* | ||
6. **CHILDREN**: | ||
- *Recursively applies patches to each child element, matching their order.* | ||
|
||
The function uses these patches to make precise updates to the DOM, ensuring minimal re-rendering. | ||
|
||
## 4. ~ Last | ||
These codes were created to help me understand the basic principles and implementation of a virtual DOM. By building each part—from creating virtual nodes to rendering, diffing, and updating the DOM—I’ve gained a clearer sense of how a virtual DOM operates at its core. However, this is just a simplified version, far from a robust, efficient virtual DOM system used in real-world applications. | ||
|
||
There are still many limitations in this implementation, such as optimizations for performance, deep-level diff algorithm improvements, event handling, and compatibility issues. The diff and patch methods here don’t handle certain edge cases and would struggle with more complex application scenarios. Even so, this exercise has strengthened my grasp of the virtual DOM’s core concepts and given me confidence to explore more advanced implementations and optimizations. | ||
|
||
In summary, this is just a learning exercise and has much room for improvement before it could be considered a mature virtual DOM implementation. Moving forward, I hope to build on this foundation and create a more efficient and comprehensive virtual DOM system. | ||
|
||
--- | ||
<div align="right"> | ||
|
||
###### *Last Modified by [SeeChen](https://github.com/SeeChen/) @ 06-NOV-2024 19:38 UTC +08:00* | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Document</title> | ||
|
||
<script src="index.js"></script> | ||
</head> | ||
<body> | ||
<div id="runVirtualDOM"> | ||
<!-- comment --> | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
|
||
function createElementa( | ||
tag, | ||
props, | ||
children | ||
) { | ||
|
||
return { | ||
tag, | ||
props: props || {}, | ||
children: children || [] | ||
} | ||
} | ||
|
||
function renderElement(vNode) { | ||
|
||
if (typeof vNode === "string") { | ||
|
||
return document.createTextNode(vNode); | ||
} | ||
|
||
const el = document.createElement(vNode.tag); | ||
for (const [ key, value ] of Object.entries(vNode.props)) { | ||
el.setAttribute(key, value); | ||
} | ||
|
||
vNode.children.forEach(child => { | ||
el.appendChild(renderElement(child)); | ||
}); | ||
|
||
return el; | ||
} | ||
|
||
function diff(oldNode, newNode) { | ||
|
||
const patches = []; | ||
if (oldNode === undefined || newNode === undefined) { | ||
|
||
if (newNode !== undefined) { | ||
patches.push({ type: "ADD", newNode }); | ||
} else if (oldNode !== undefined) { | ||
patches.push({ type: "REMOVE" }); | ||
} | ||
} | ||
|
||
else if (typeof oldNode === "string" && typeof newNode === "string") { | ||
if (oldNode !== newNode) { | ||
patches.push({ type: "TEXT", text: newNode }); | ||
} | ||
} | ||
|
||
else if (oldNode.tag !== newNode.tag) { | ||
patches.push({ type: "REPLACE", newNode }); | ||
} | ||
|
||
else { | ||
const propPatches = []; | ||
for (const [ key, value ] of Object.entries(newNode.props)) { | ||
if (oldNode.props[key] !== value) { | ||
propPatches.push({ key, value }); | ||
} | ||
} | ||
|
||
for (const key in oldNode.props) { | ||
if (!(key in newNode.props)) { | ||
propPatches.push({ key }); | ||
} | ||
} | ||
|
||
if (propPatches.length > 0) { | ||
patches.push({ type: "PROPS", props: propPatches }) | ||
} | ||
|
||
const childPatch = [] | ||
const maxChildrenLength = Math.max(oldNode.children.length, newNode.children.length); | ||
for (let i = 0; i < maxChildrenLength; i++) { | ||
childPatch.push(diff(oldNode.children[i], newNode.children[i])); | ||
} | ||
patches.push({ type: "CHILDREN", children: childPatch }); | ||
} | ||
|
||
return patches; | ||
} | ||
|
||
function patch(parent, patches, index = 0) { | ||
|
||
const el = parent.children[index]; | ||
|
||
patches.forEach(_patch => { | ||
switch(_patch.type) { | ||
case "ADD": | ||
parent.appendChild(renderElement(_patch.newNode)); | ||
break; | ||
case "REMOVE": | ||
parent.removeChild(el); | ||
break; | ||
case "TEXT": | ||
el.textContent = _patch.text; | ||
break; | ||
case "REPLACE": | ||
parent.replaceChild(renderElement(_patch.newNode), el); | ||
break; | ||
case "PROPS": | ||
_patch.props.forEach(({ key, value }) => { | ||
if (value === undefined){ | ||
el.removeAttribute(key); | ||
} else { | ||
el.setAttribute(key, value); | ||
} | ||
}); | ||
break; | ||
case "CHILDREN": | ||
_patch.children.forEach((childPatch, i) => { | ||
patch(el, childPatch, i); | ||
}); | ||
break; | ||
} | ||
}); | ||
} | ||
|
||
window.onload = function () { | ||
|
||
const runVirtualDOM = document.getElementById("runVirtualDOM"); | ||
let oldDom = createElementa("div", {}, [ | ||
createElementa("p", {}, ["Hello World!"]), | ||
createElementa("ol", { | ||
start: 0 | ||
}, [ | ||
createElementa("li", { | ||
class: "aaa", | ||
}, ["你好世界!"]), | ||
]), | ||
]); | ||
|
||
console.log("Create Element Node:"); | ||
console.log(oldDom); | ||
|
||
console.log("Render to browser:"); | ||
console.log(renderElement(oldDom)); | ||
|
||
runVirtualDOM.appendChild(renderElement(oldDom)); | ||
|
||
let newDom = createElementa("div", {}, [ | ||
createElementa("p", {}, ["Hello World!"]), | ||
createElementa("ol", { | ||
start: 0 | ||
}, [ | ||
createElementa("li", {}, ["你好世界!"]), | ||
createElementa("li", { | ||
class: "aaa", | ||
}, ["你好世界!!"]), | ||
]), | ||
createElementa("p", { | ||
style: "color: #adb", | ||
}, ["P tag"]) | ||
]); | ||
|
||
console.log("New Element Node:"); | ||
console.log(newDom); | ||
|
||
let domPatch = diff(oldDom, newDom); | ||
console.log("Diffrent"); | ||
console.log(domPatch); | ||
|
||
setTimeout(() => { | ||
patch(runVirtualDOM, domPatch); | ||
}, 5000); | ||
} |