创建React元素类
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);
}
export { Element, createElement };
渲染React元素至页面
import { Element } from "./element.js";
function setAttr(ele, key, val) {
switch (key) {
case "value":
if (ele.tagName == ("INPUT" || "TEXTAREA")) {
ele.value = val;
} else {
ele.setAttribute(key, val);
}
break;
case "className":
ele.setAttribute("class", val);
break;
default:
ele.setAttribute(key, val);
break;
}
}
function render(elementObject) {
let ele = document.createElement(elementObject.type);
for (const key in elementObject.props) {
setAttr(ele, key, elementObject.props[key]);
}
elementObject.children.forEach((child) => {
let childNode;
if (child instanceof Element) {
childNode = render(child);
} else {
childNode = document.createTextNode(child);
}
renderDOM(childNode, ele);
});
return ele;
}
function renderDOM(ele, target) {
target.appendChild(ele);
return ele;
}
export { setAttr, render, renderDOM };
diff算法核心
let Index = 0;
function diff(oldTree, newTree) {
let patches = {};
treeWalker(oldTree, newTree, patches, Index);
return patches;
}
function treeWalker(oldNode, newNode, patches, index) {
let currentPatch = [];
if (!newNode) {
currentPatch.push({ type: "REMOVE", index });
} else if (isString(oldNode) && isString(newNode)) {
currentPatch.push({ type: "TEXT", text: newNode });
} else if (oldNode.type === newNode.type) {
let attrs = diffAttr(oldNode.props, newNode.props);
if (Object.keys(attrs).length) {
currentPatch.push({ type: "ATTRS", attrs });
}
diffChildren(oldNode.children, newNode.children, patches);
} else {
currentPatch.push({ type: "REPLACE", newNode: newNode });
}
if (currentPatch.length > 0) {
patches[index] = currentPatch;
}
}
function diffAttr(oldAttrs, newAttrs) {
let patch = {};
for (const key in oldAttrs) {
if (oldAttrs[key] !== newAttrs[key]) {
patch[key] = newAttrs[key];
}
}
for (const key in newAttrs) {
if (!oldAttrs.hasOwnProperty(key)) {
patch[key] = newAttrs[key];
}
}
return patch;
}
function diffChildren(oldChildren, newChildren, patches) {
oldChildren.forEach((child, idx) => {
treeWalker(child, newChildren[idx], patches, ++Index);
});
}
function isString(node) {
return Object.prototype.toString.call(node) == "[object String]";
}
export { diff, isString };
获取diff算法返回的补丁,为dom打上补丁
import { render } from "./render.js";
let Index = 0;
function patch(node, patches) {
walker(node, patches, Index);
}
function walker(node, patches, index) {
let patch = patches[index++];
let childNodes = node.childNodes;
childNodes.forEach((child) => {
walker(child, patches, index++);
});
if (patch) {
doPatch(node, patch);
}
}
function doPatch(node, patch) {
if (patch) {
patch.forEach((item) => {
switch (item.type) {
case "ATTRS":
for (const key in patch.attrs) {
let value = patch.attrs[key];
node.setAttribute(key, value);
}
break;
case "TEXT":
node.nodeValue = item.text;
break;
case "REMOVE":
node.parentNode.removeChild(node);
break;
case "REPLACE":
let newNode = render(item.newNode);
node.parentNode.replaceChild(newNode, node);
break;
default:
break;
}
});
}
}
export default patch;
试验一下diff算法能否成功
import { renderDOM, render } from "./src/DOM.js";
import { createElement } from "./src/element.js";
import { diff } from "./src/diff.js";
import patch from "./src/patch.js";
let VNode = createElement("ul", { className: "father" }, [
createElement("li", { className: "child" }, ["a"]),
createElement("li", { className: "child" }, ["b"]),
createElement("li", { className: "child" }, ["c"]),
]);
let el = renderDOM(render(VNode), document.getElementById("app"));
let VNode2 = createElement(
"div",
{ className: "father2", style: "color:red" },
[
createElement("div", { className: "child" }, ["d"]),
createElement("div", { className: "child" }, ["f"]),
createElement("h1", { className: "child" }, ["g"]),
]
);
let patches = diff(VNode, VNode2);
patch(el, patches);