Skip to content

Commit f266dd1

Browse files
committed
feat: patchComponent
1 parent 81b41b5 commit f266dd1

File tree

6 files changed

+436
-57
lines changed

6 files changed

+436
-57
lines changed

index (2).html

+245-35
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,46 @@
192192
}
193193
const mountStatefulComponent = function (vnode, container, isSVG) {
194194
// 创建组件实例
195-
const instance = new vnode.tag()
196-
// 渲染vnode
197-
instance.$vnode = instance.render(h)
198-
// 挂载
199-
mount(instance.$vnode, container, isSVG)
200-
// el 属性值 和 组件实例的 $el 属性都引用组件的根DOM元素
201-
instance.$el = vnode.el = instance.$vnode.el
195+
const instance = (vnode.children = new vnode.tag())
196+
instance.$props = vnode.data
197+
instance._update = function () {
198+
// 如果 instance._mounted 为真,说明组件已挂载,应该执行更新操作
199+
if (instance._mounted) {
200+
// 1、拿到旧的 VNode
201+
const prevVNode = instance.$vnode
202+
// 2、重渲染新的 VNode
203+
const nextVNode = (instance.$vnode = instance.render())
204+
// 3、patch 更新
205+
patch(prevVNode, nextVNode, prevVNode.el.parentNode)
206+
// 4、更新 vnode.el 和 $el
207+
instance.$el = vnode.el = instance.$vnode.el
208+
} else {
209+
// 1、渲染VNode
210+
instance.$vnode = instance.render()
211+
// 2、挂载
212+
mount(instance.$vnode, container, isSVG)
213+
// 3、组件已挂载的标识
214+
instance._mounted = true
215+
// 4、el 属性值 和 组件实例的 $el 属性都引用组件的根DOM元素
216+
instance.$el = vnode.el = instance.$vnode.el
217+
// 5、调用 mounted 钩子
218+
instance.mounted && instance.mounted()
219+
}
220+
}
221+
222+
instance._update()
223+
202224
}
203225

204226
const mountFunctionalComponent = function (vnode, container, isSVG) {
205-
const $vnode = vnode.tag()
206-
mount($vnode, container, isSVG)
207-
vnode.el = $vnode.el
227+
// 获取 props
228+
const props = vnode.data
229+
// 获取 VNode
230+
const $vnode = (vnode.children = vnode.tag(props))
231+
// 挂载
232+
mount($vnode, container, isSVG)
233+
// el 元素引用该组件的根元素
234+
vnode.el = $vnode.el
208235
}
209236

210237
const mountComponent = function (vnode, container, isSVG) {
@@ -283,11 +310,181 @@
283310

284311
const replaceVNode = function (prevVNode, nextVNode, container) {
285312
container.removeChild(prevVNode.el)
313+
// 如果将要被移除的 VNode 类型是组件,则需要调用该组件实例的 unmounted 钩子函数
314+
if (prevVNode.flags & VNodeFlags.COMPONENT_STATEFUL_NORMAL) {
315+
// 类型为有状态组件的 VNode,其 children 属性被用来存储组件实例对象
316+
const instance = prevVNode.children
317+
instance.unmounted && instance.unmounted()
318+
}
286319
mount(nextVNode, container)
287320
}
288321

322+
const patchData = function (el, key, prevValue, nextValue, isSVG) {
323+
switch (key) {
324+
case 'style':
325+
for (let k in nextValue) {
326+
el.style[k] = nextValue[k]
327+
}
328+
for (let k in prevValue) {
329+
if (!nextValue || !nextValue.hasOwnProperty(k)) {
330+
el.style[k] = ''
331+
}
332+
}
333+
break
334+
case 'class':
335+
if (nextValue) {
336+
if (isSVG) {
337+
el.setAttribute('class', serialization(nextValue))
338+
} else {
339+
el.className = serialization(nextValue)
340+
}
341+
}
342+
break
343+
default:
344+
const domPropsRE = /\[A-Z]|^(?:value|checked|selected|muted)$/
345+
if (key[0] === 'o' && key[1] === 'n') {
346+
// 事件
347+
// 移除旧事件
348+
if (prevValue) {
349+
el.removeEventListener(key.slice(2), prevValue)
350+
}
351+
// 添加新事件
352+
if (nextValue) {
353+
el.addEventListener(key.slice(2), nextValue)
354+
}
355+
} else if (domPropsRE.test(key)) {
356+
// 当作 DOM Prop 处理
357+
el[key] = nextValue
358+
} else {
359+
// 当作 Attr 处理
360+
el.setAttribute(key, nextValue)
361+
}
362+
break
363+
}
364+
}
365+
366+
// 3 * 3 = 9种情况
367+
const patchChildren = function (prevChildFlags, nextChildFlags, prevChildren, nextChildren, container) {
368+
switch (prevChildFlags) {
369+
case ChildrenFlags.SINGLE_VNODE:
370+
switch (nextChildFlags) {
371+
case ChildrenFlags.SINGLE_VNODE:
372+
patch(prevChildren, nextChildren, container)
373+
break
374+
case ChildrenFlags.NO_CHILDREN:
375+
container.removeChild(prevChildren.el)
376+
break
377+
default:
378+
container.removeChild(prevChildren.el)
379+
for (let i = 0; i < nextChildren.length; i++) {
380+
mount(nextChildren[i], container)
381+
}
382+
break
383+
}
384+
break
385+
case ChildrenFlags.NO_CHILDREN:
386+
switch (nextChildFlags) {
387+
case ChildrenFlags.SINGLE_VNODE:
388+
mount(nextChildren, container)
389+
break
390+
case ChildrenFlags.NO_CHILDREN:
391+
break
392+
default:
393+
for (let i = 0; i < nextChildren.length; i++) {
394+
mount(nextChildren[i], container)
395+
}
396+
break
397+
}
398+
break
399+
default:
400+
switch (nextChildFlags) {
401+
case ChildrenFlags.SINGLE_VNODE:
402+
for(let i = 0; i < prevChildren.length; i++) {
403+
container.removeChild(prevChildren[i].el)
404+
}
405+
mount(nextChildren, container)
406+
break
407+
case ChildrenFlags.NO_CHILDREN:
408+
for(let i = 0; i < prevChildren.length; i++) {
409+
container.removeChild(prevChildren[i].el)
410+
}
411+
break
412+
default:
413+
// 遍历旧的子节点,将其全部移除
414+
for (let i = 0; i < prevChildren.length; i++) {
415+
container.removeChild(prevChildren[i].el)
416+
}
417+
// 遍历新的子节点,将其全部添加
418+
for (let i = 0; i < nextChildren.length; i++) {
419+
mount(nextChildren[i], container)
420+
}
421+
break
422+
}
423+
break
424+
}
425+
}
426+
289427
const patchElement = function (prevVNode, nextVNode, container) {
290-
console.log('hhhhhhhhhhh')
428+
if (prevVNode.tag !== nextVNode.tag) {
429+
replaceVNode(prevVNode, nextVNode, container)
430+
return
431+
}
432+
// 拿到 el 元素,注意这时要让 nextVNode.el 也引用该元素
433+
const el = (nextVNode.el = prevVNode.el)
434+
// 拿到 新旧 VNodeData
435+
const prevData = prevVNode.data
436+
const nextData = nextVNode.data
437+
// 新的 VNodeData 存在时才有必要更新
438+
if (nextData) {
439+
// 遍历新的 VNodeData
440+
for (let key in nextData) {
441+
// 根据 key 拿到新旧 VNodeData 值
442+
const prevValue = prevData[key]
443+
const nextValue = nextData[key]
444+
patchData(el, key, prevValue, nextValue)
445+
}
446+
}
447+
if (prevData) {
448+
// 遍历旧的 VNodeData,将已经不存在于新的 VNodeData 中的数据移除
449+
for (let key in prevData) {
450+
const prevValue = prevData[key]
451+
if (prevValue && (!nextData || !nextData.hasOwnProperty(key))) {
452+
// 第四个参数为 null,代表移除数据
453+
patchData(el, key, prevValue, null)
454+
}
455+
}
456+
}
457+
458+
// 调用 patchChildren 函数递归地更新子节点
459+
patchChildren(
460+
prevVNode.childFlags, // 旧的 VNode 子节点的类型
461+
nextVNode.childFlags, // 新的 VNode 子节点的类型
462+
prevVNode.children, // 旧的 VNode 子节点
463+
nextVNode.children, // 新的 VNode 子节点
464+
el // 当前标签元素,即这些子节点的父节点
465+
)
466+
}
467+
468+
const patchText = function (prevVNode, nextVNode) {
469+
const el = (nextVNode.el = prevVNode.el)
470+
if (nextVNode.children !== prevVNode.children) {
471+
el.nodeValue = nextVNode.children
472+
}
473+
}
474+
475+
const patchComponent = function (prevVNode, nextVNode, container) {
476+
if (nextVNode.tag !== prevVNode.tag) {
477+
replaceVNode(prevVNode, nextVNode, container)
478+
} else if (nextVNode.flags & VNodeFlags.COMPONENT_STATEFUL_NORMAL) {
479+
// 1、获取组件实例
480+
const instance = (nextVNode.children = prevVNode.children)
481+
// 2、更新 props
482+
instance.$props = nextVNode.data
483+
// 3、更新组件
484+
instance._update()
485+
} else {
486+
console.log('hehe')
487+
}
291488
}
292489

293490
const patch = function (prevVNode, nextVNode, container) {
@@ -300,7 +497,7 @@
300497
} else if (nextFlags & VNodeFlags.COMPONENT) {
301498
patchComponent(prevVNode, nextVNode, container)
302499
} else if (nextFlags & VNodeFlags.TEXT) {
303-
patchText(prevVNode, nextVNode, container)
500+
patchText(prevVNode, nextVNode)
304501
} else if (nextFlags & VNodeFlags.FRAGMENT) {
305502
patchFragment(prevVNode, nextVNode, container)
306503
} else if (nextFlags & VNodeFlags.PORTAL) {
@@ -326,22 +523,41 @@
326523
}
327524
}
328525

329-
class MyComponent {
526+
class ParentComponent {
527+
localMsg = 'from parent'
528+
isTrust = false
529+
530+
mounted () {
531+
setTimeout(() => {
532+
this.localMsg = '2s after'
533+
this.isTrust = true
534+
this._update()
535+
}, 2000);
536+
}
330537
render () {
331-
return h('div',
332-
{
333-
style: {
334-
background: 'green'
335-
}
336-
},
337-
[
338-
h('span', null, '我是组件的标题1......'),
339-
h('span', null, '我是组件的标题2......')
340-
]
341-
)
538+
const text = this.localMsg
539+
return h(MyFunctionalComponent, { text: this.localMsg })
342540
}
343541
}
344-
function MyFunctionalComponent() {
542+
class ChildComponent1 {
543+
render () {
544+
return h('div', null, this.$props.text)
545+
}
546+
}
547+
548+
class ChildComponent2 {
549+
render () {
550+
return h('div', null, '222222222')
551+
}
552+
}
553+
554+
function MyParent (props) {
555+
return h(
556+
MyFunctionalComponent,
557+
{ text: props.text }
558+
)
559+
}
560+
function MyFunctionalComponent(props) {
345561
// 返回要渲染的内容描述,即 VNode
346562
return h(
347563
'div',
@@ -350,19 +566,13 @@
350566
background: 'red'
351567
}
352568
},
353-
[
354-
h('span', null, '我是组件的标题1......'),
355-
h('span', null, '我是组件的标题2......')
356-
]
569+
props.text
357570
)
358571
}
359-
const dynamicClass1 = { 'a': true, 'b': false }
360-
const dynamicClass2 = [ 'c', 'd' ]
361-
const vnode = h('div', { style: { color: 'red' }, class: ['test', dynamicClass1], onClick: function () { console.log('click me') } }, [h('div', null, [h('span', null, 'kkk'), h(Fragment, null, null)])])
572+
573+
const vnode = h(ParentComponent, { text: '函数式组件' }, null)
362574

363575
render(vnode, document.getElementById('app'))
364-
setTimeout(() => {
365-
render(h('div', null, [h(MyFunctionalComponent, null, null), h(MyComponent, null, null)]), document.getElementById('app'))
366-
}, 1000)
576+
367577

368578
</script>

index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
* @Author: astar
33
* @Date: 2021-05-19 15:23:32
44
* @LastEditors: astar
5-
* @LastEditTime: 2021-05-20 19:09:54
5+
* @LastEditTime: 2021-05-25 20:17:47
66
* @Description: 文件描述
77
* @FilePath: \vue\index.js
88
*/
99
import { render } from './packages/render.js'
10-
import { h } from './packages/createVNode.js'
10+
import { h } from './packages/h.js'
1111

1212

1313

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "index.js",
66
"scripts": {
77
"start": "node ./index.js",
8-
"build": "webpack"
8+
"build": "webpack --mode=production"
99
},
1010
"keywords": [
1111
"vue"

packages/createVNode.js packages/h.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
* @Author: astar
33
* @Date: 2021-05-19 18:14:57
44
* @LastEditors: astar
5-
* @LastEditTime: 2021-05-24 14:30:57
5+
* @LastEditTime: 2021-05-25 20:18:09
66
* @Description: 文件描述
7-
* @FilePath: \vue\packages\createVNode.js
7+
* @FilePath: \vue\packages\h.js
88
*/
99
import { VNodeFlags, ChildrenFlags, Fragment, Portal } from '../config/consts.js'
1010

0 commit comments

Comments
 (0)