Skip to content

Commit ff8b114

Browse files
committed
✨feat: 完善9.6_移除不存在的元素
1 parent d4a33d3 commit ff8b114

File tree

1 file changed

+312
-0
lines changed

1 file changed

+312
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Document</title>
8+
</head>
9+
10+
<body>
11+
<div id="app"></div>
12+
13+
<script>
14+
15+
function shouldSetAsProps (el, key, value) {
16+
if (key === 'form' && el.tagName === 'INPUT') return false
17+
return key in el
18+
}
19+
20+
function createRenderer (options) {
21+
22+
const {
23+
createElement,
24+
insert,
25+
setElementText,
26+
patchProps,
27+
createText,
28+
setText
29+
} = options
30+
31+
function mountElement (vnode, container, anchor) {
32+
const el = vnode.el = createElement(vnode.type)
33+
if (typeof vnode.children === 'string') {
34+
setElementText(el, vnode.children)
35+
} else if (Array.isArray(vnode.children)) {
36+
vnode.children.forEach(child => {
37+
patch(null, child, el)
38+
})
39+
}
40+
41+
if (vnode.props) {
42+
for (const key in vnode.props) {
43+
patchProps(el, key, null, vnode.props[key])
44+
}
45+
}
46+
47+
insert(el, container, anchor)
48+
}
49+
50+
function patchChildren (n1, n2, container) {
51+
if (typeof n2.children === 'string') {
52+
if (Array.isArray(n1.children)) {
53+
n1.children.forEach((c) => unmount(c))
54+
}
55+
setElementText(container, n2.children)
56+
} else if (Array.isArray(n2.children)) {
57+
const oldChildren = n1.children
58+
const newChildren = n2.children
59+
60+
// 用来存储寻找过程中遇到的最大索引值
61+
let lastIndex = 0
62+
// 遍历新的 children
63+
for (let i = 0;i < newChildren.length;i++) {
64+
65+
const newVNode = newChildren[i];
66+
// 遍历旧的 children
67+
// 在第一层循环中定义变量 find,代表是否在旧的一组子节点中找到可复用的节点,
68+
// 初始值为 false,代表没找到
69+
let find = false
70+
for (let j = 0;j < oldChildren.length;j++) {
71+
const oldVNode = oldChildren[j]
72+
// 如果找到了具有相同 key 值的两个节点,说明可以复用但仍然需要调用 patch 函数更新
73+
if (newVNode.key === oldVNode.key) {
74+
// 一旦找到可复用的节点,则将变量 find 的值设为 true
75+
find = true
76+
patch(oldVNode, newVNode, container)
77+
if (j < lastIndex) {
78+
// 如果当前找到的旧 children 中 的索引小于最大索引值 lastIndex,
79+
// 说明改节点对应的真实 DOM 需要 移动
80+
const prevVNode = newChildren[i - 1]
81+
// 如果 prevVNode 不存在,则说明当前 newVNode 是第一个节点,它不需要移动
82+
if (prevVNode) {
83+
// 由于我们要将 newVNode 对应的真实 DOM 移动到 prevVNode 所对应真实 DOM 后面,
84+
// 所以我们需要获取 prevVNode 所对应真实 DOM 的下一个兄弟节点,并将其作为锚点
85+
const anchor = prevVNode.el.nextSibling
86+
// 调用 insert 方法将 newVNode 对应的真实 DOM 插入到锚点元素前面,
87+
// 也就是 prevVNode 对应真实 DOM 的后面
88+
insert(newVNode.el, container, anchor)
89+
}
90+
} else {
91+
// 如果当前找到的节点在旧 children 中的索引不小于最大索引值,
92+
// 则更新 lastIndex 的值
93+
lastIndex == j
94+
}
95+
break // 这里需要 break
96+
}
97+
}
98+
// 如果代码运行到这里,find 仍然为 false,
99+
// 说明当前 newVNode 没有在旧的一组子节点中找到可复用的节点
100+
// 也就是说,当前 newVNode 是新增节点,需要挂载
101+
if (!find) {
102+
// 为了将节点挂载到正确位置,我们需要先获取锚点元素
103+
// 首先获取当前 newVNode 的前一个 vnode 节点
104+
const prevVNode = newChildren[i - 1]
105+
let anchor = null
106+
if (prevVNode) {
107+
// 如果有前一个 vnode 节点,则使用它的下一个兄弟节点作为锚点元素
108+
anchor = prevVNode.el.nextSibling
109+
} else {
110+
// 如果没有前一个 vnode 节点,说明即将挂载的新节点是第一个子节点
111+
// 这时我们使用容器元素的 firstChild 作为锚点
112+
anchor = container.firstChild
113+
}
114+
// 挂载 newVNode
115+
patch(null, newVNode, container, anchor)
116+
}
117+
}
118+
// 上一步的更新操作完成后
119+
// 遍历旧的一组子节点
120+
for (let i = 0;i < oldChildren.length;i++) {
121+
const oldVNode = oldChildren[i]
122+
// 拿旧子节点 oldVNode 去新的一组子节点中寻找具有相同 key 值的节点
123+
const has = newChildren.find(
124+
vnode => vnode.key === oldVNode.key
125+
)
126+
if (!has) {
127+
// 如果没有找到具有相同 key 值的节点,则说明需要删除该节点
128+
// 调用 unmount 函数将其卸载
129+
unmount(oldVNode)
130+
}
131+
}
132+
} else {
133+
if (Array.isArray(n1.children)) {
134+
n1.children.forEach(c => unmount(c))
135+
} else if (typeof n1.children === 'string') {
136+
setElementText(container, '')
137+
}
138+
}
139+
}
140+
141+
function patchElement (n1, n2) {
142+
// 新的 vnode 也引用了真实 DOM 元素
143+
const el = n2.el = n1.el
144+
const oldProps = n1.props
145+
const newProps = n2.props
146+
147+
for (const key in newProps) {
148+
if (newProps[key] !== oldProps[key]) {
149+
patchProps(el, key, oldProps[key], newProps[key])
150+
}
151+
}
152+
for (const key in oldProps) {
153+
if (!(key in newProps)) {
154+
patchProps(el, key, oldProps[key], null)
155+
}
156+
}
157+
158+
patchChildren(n1, n2, el)
159+
}
160+
161+
function unmount (vnode) {
162+
if (vnode.type === Fragment) {
163+
vnode.children.forEach(c => unmount(c))
164+
return
165+
}
166+
const parent = vnode.el.parentNode
167+
if (parent) {
168+
parent.removeChild(vnode.el)
169+
}
170+
}
171+
172+
function patch (n1, n2, container, anchor) {
173+
if (n1 && n1.type !== n2.type) {
174+
unmount(n1)
175+
n1 = null
176+
}
177+
178+
const { type } = n2
179+
180+
if (typeof type === 'string') {
181+
if (!n1) {
182+
mountElement(n2, container, anchor)
183+
} else {
184+
patchElement(n1, n2)
185+
}
186+
} else if (type === Text) {
187+
if (!n1) {
188+
const el = n2.el = createText(n2.children)
189+
insert(el, container)
190+
} else {
191+
const el = n2.el = n1.el
192+
if (n2.children !== n1.children) {
193+
setText(el, n2.children)
194+
}
195+
}
196+
} else if (type === Fragment) {
197+
if (!n1) {
198+
n2.children.forEach(c => patch(null, c, container))
199+
} else {
200+
patchChildren(n1, n2, container)
201+
}
202+
}
203+
}
204+
205+
function render (vnode, container) {
206+
if (vnode) {
207+
// 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
208+
patch(container._vnode, vnode, container)
209+
} else {
210+
if (container._vnode) {
211+
// 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
212+
unmount(container._vnode)
213+
}
214+
}
215+
// 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
216+
container._vnode = vnode
217+
}
218+
219+
return {
220+
render
221+
}
222+
}
223+
224+
const renderer = createRenderer({
225+
createElement (tag) {
226+
return document.createElement(tag)
227+
},
228+
setElementText (el, text) {
229+
el.textContent = text
230+
},
231+
insert (el, parent, anchor = null) {
232+
// insertBefore 需要锚点元素 anchor
233+
parent.insertBefore(el, anchor)
234+
},
235+
createText (text) {
236+
return document.createTextNode(text)
237+
},
238+
setText (el, text) {
239+
el.nodeValue = text
240+
},
241+
patchProps (el, key, prevValue, nextValue) {
242+
if (/^on/.test(key)) {
243+
const invokers = el._vei || (el._vei = {})
244+
let invoker = invokers[key]
245+
const name = key.slice(2).toLowerCase()
246+
if (nextValue) {
247+
if (!invoker) {
248+
invoker = el._vei[key] = (e) => {
249+
console.log(e.timeStamp)
250+
console.log(invoker.attached)
251+
if (e.timeStamp < invoker.attached) return
252+
if (Array.isArray(invoker.value)) {
253+
invoker.value.forEach(fn => fn(e))
254+
} else {
255+
invoker.value(e)
256+
}
257+
}
258+
invoker.value = nextValue
259+
invoker.attached = performance.now()
260+
el.addEventListener(name, invoker)
261+
} else {
262+
invoker.value = nextValue
263+
}
264+
} else if (invoker) {
265+
el.removeEventListener(name, invoker)
266+
}
267+
} else if (key === 'class') {
268+
el.className = nextValue || ''
269+
} else if (shouldSetAsProps(el, key, nextValue)) {
270+
const type = typeof el[key]
271+
if (type === 'boolean' && nextValue === '') {
272+
el[key] = true
273+
} else {
274+
el[key] = nextValue
275+
}
276+
} else {
277+
el.setAttribute(key, nextValue)
278+
}
279+
}
280+
})
281+
282+
const VNode1 = {
283+
type: 'div',
284+
children: [
285+
{ type: 'p', children: '1', key: 1 },
286+
{ type: 'p', children: '2', key: 2 },
287+
{ type: 'p', children: 'hello', key: 3 }
288+
]
289+
}
290+
renderer.render(VNode1, document.querySelector('#app'))
291+
292+
const VNode2 = {
293+
type: 'div',
294+
children: [
295+
{ type: 'p', children: '4', key: 4 },
296+
{ type: 'p', children: 'world', key: 3 },
297+
{ type: 'p', children: '1', key: 1 },
298+
{ type: 'p', children: '2', key: 2 }
299+
]
300+
}
301+
302+
setTimeout(() => {
303+
console.log('update')
304+
renderer.render(VNode2, document.querySelector('#app'))
305+
}, 400);
306+
307+
308+
309+
</script>
310+
</body>
311+
312+
</html>

0 commit comments

Comments
 (0)