-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathh-mount-patch.html
123 lines (112 loc) · 3.14 KB
/
h-mount-patch.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<div id="app"></div>
<button onclick="patch(vdom,vdom2)">patch</button>
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>
<script>
function h(tag, props, children) {
return {
tag,
props,
children,
};
}
function mount(vnode, container) {
const el = (vnode.el = document.createElement(vnode.tag));
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
el.setAttribute(key, value);
}
}
if (vnode.children) {
if (typeof vnode.children === "string") {
el.textContent = vnode.children;
} else {
vnode.children.forEach((child) => {
mount(child, el);
});
}
}
container.appendChild(el);
}
const vdom = h("div", { class: "red" }, [h("span", null, "hello")]);
mount(vdom, document.getElementById("app"));
function patch(oldVnode, newVnode) {
// same tag
if (newVnode.tag === oldVnode.tag) {
const el = (newVnode.el = oldVnode.el);
// props
const oldProps = oldVnode.props || {};
const newProps = newVnode.props || {};
// add new prop ?
for (const key in newProps) {
const oldValue = oldProps[key];
const newValue = newProps[key];
if (oldValue !== newValue) {
el.setAttribute(key, newValue);
}
}
// remove old prop ?
for (const key in oldProps) {
if (!(key in newProps)) {
el.removeAttribute(key);
}
}
// children
const oldChildren = oldVnode.children;
const newChildren = newVnode.children;
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
// replace old children
el.textContent = newChildren;
}
} else {
// replace old children
el.textContent = newChildren;
}
} else {
if (typeof oldChildren === "string") {
// clear old html
el.innerHTML = "";
// add new children to el
newChildren.forEach((child) => {
mount(child, el);
});
} else {
// patch same index
const commonLength = Math.min(oldChildren.length, newChildren.length);
for (let i = 0; i < commonLength; i++) {
// patch difference
patch(oldChildren[i], newChildren[i]);
}
if (newChildren.length > oldChildren.length) {
newChildren.slice(oldChildren.length).forEach((child) => {
mount(child, el);
});
}
if (newChildren.length < oldChildren.length) {
oldChildren.slice(newChildren.length).forEach((child) => {
el.removeChild(child.el);
});
}
}
}
} else {
// replace
// get parent node
const container = oldVnode.el.parentNode;
// clear current node
container.innerHTML = "";
mount(newVnode, container);
}
}
const vdom2 = h("div", { class: "green" }, [h("span", null, "changed!")]);
// patch(vdom, vdom2);
</script>