总结
- 流程总结
通过render函数进行ELEMENT节点的挂载大致就是下面是大致流程:
**ELEMENT节点的挂载→render函数→patch挂载函数→type+shapeFlag→processElement→n1==null→patchElement**→ el = (n2.el = n1.el!)并记录新旧vnode的props→开始更新孩子节点patchChildren→ 获取新旧节点的children和shapeFlag→hostSetElementText(container, c2 as string) →更新孩子节点结束→开始更新props patchProps→两次for循环,一次挂载新节点的props,一次删除只有旧节点存在的属性→props更新结束
- 函数总结
- render函数:判断vnode是否为空,进行patch挂载还是unmount卸载操作。
- patch函数:根据vnode的type和shapeFlag进行不同的processXXX操作,本节中是processElement
- processElement函数:根据vnode1(n1)是否为空进行mountElement挂载、patchElement更新
- patchElement函数(同DOM):更新children节点、更新props(挂载新节点的属性、删除存在于旧节点的属性)
代码测试样例
<div id="app"></div>
<script>
const app = document.querySelector('#app')
const { render, h } = Vue
const vnode1 = h(
'div',
{
class: 'test'
},
'hello render'
)
render(vnode1, app) // ELEMENT的挂载
setTimeout(() => { // ELEMENT(同DOM)的更新
const vnode2 = h(
'div',
{
class: 'active'
},
'render update'
)
render(vnode2, app)
}, 2000)
</script>
函数内部实现流程
- render函数,如果传递了第一个参数,则进行patch更新操作,否则进行unmount卸载操作
render函数
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true) // 卸载
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG) //更新
}
flushPostFlushCbs()
container._vnode = vnode
}
- 由于我们进行的是ELEMENT的挂载操作,所以直接进入patch函数,patch函数中主要是判断**
vnode.type和vnode.shapeFlag**来进行不同的处理,这里是进行ELEMENT的处理。
patch函数
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
if (n1 === n2) { // 1. 判断新旧元素是否相同,相同则不进行操作
return
}
const { type, ref, shapeFlag } = n2 //解构传入的参数,就是render的第一个参数vnode
switch (type) { //根据不同的type进行不同的操作
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
default://匹配其他的项(非文本类)
if (shapeFlag & ShapeFlags.ELEMENT) { //shapeFlag & ShapeFlags.xxx 使用二进制的与运算,这里是对vnode中的shapeFlag进行分解,获取创建vnode时使用的DOM
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) { // 处理组件
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) { // 处理teleport
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized,
internals
)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
}
- 进入processElement,如果n1是空则进行mountElement函数,反之则进入patchElement函数,当前n1是空,进入mountElement函数。判断旧节点是否存在进行挂载和更新操作。
processElement函数
const processElement = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
) => {
if (n1 == null) {
mountElement( // 挂载
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
patchElement( // 更新
n1,
n2,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
-
进入patchElement更新函数(同DOM),进行元素节点的赋值
const el = (n2.el = n1.el!),定义变量存储新旧vnode的props对象,调用patchChildren进行孩子节点的更新,调用patchProps进行props的更新。patchElement函数总结:
- 元素节点复制
const el = (n2.el = n1.el!) - patchChildren函数:更新children内容
- patchProps函数:更新props内容
- 元素节点复制
patchElement函数(处理同DOM)
const patchElement = (
n1: VNode,
n2: VNode,
) => {
const el = (n2.el = n1.el!)
let { patchFlag, dynamicChildren, dirs } = n2
const oldProps = n1.props || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
// 更新子节点
patchChildren(
n1,
n2,
el,
null,
parentComponent,
parentSuspense,
areChildrenSVG,
slotScopeIds,
false
)
// 更新props属性
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
}
- patchChildren函数,使用c1、c2记录n1、n2的children属性,并记录n1、n2的shapeFlag。然后根据n2.shapeFlag值进行分解,最终匹配到hostSetElementText,直接进行元素文本赋值。
patchChildren函数
const patchChildren: PatchChildrenFn = (
n1,
n2,
container,
anchor,
) => {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const c2 = n2.children
const { patchFlag, shapeFlag } = n2
// children has 3 possibilities: text, array or no children.
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 匹配到这里进行子节点的更新
// text children fast path
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
}
if (c2 !== c1) {
hostSetElementText(container, c2 as string) // 直接进行元素文本赋值
}
} else {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// prev children was array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// two arrays, cannot assume anything, do full diff
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else {
// no new children, just unmount old
unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)
}
} else {
// prev children was text OR null
// new children is array OR null
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(container, '')
}
// mount new if array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
}
}
- patchProps函数,处理节点props的属性。进行两次循环,第一次是挂载新节点的props到元素上,第二次是删除旧节点在元素el上的属性。
patchProps函数
const patchProps = (
el: RendererElement,
vnode: VNode,
oldProps: Data,
newProps: Data,
) => {
if (oldProps !== newProps) {
for (const key in newProps) { // el对新节点vnode的props挂载
// empty string is not valid prop
if (isReservedProp(key)) continue
const next = newProps[key]
const prev = oldProps[key]
// defer patching value
if (next !== prev && key !== 'value') {
hostPatchProp(
el,
key,
prev,
next,
isSVG,
vnode.children as VNode[],
parentComponent,
parentSuspense,
unmountChildren
)
}
}
if (oldProps !== EMPTY_OBJ) {
for (const key in oldProps) {// el对旧节点vnode的props卸载(只存在于旧节点上的,不是共有的)
if (!isReservedProp(key) && !(key in newProps)) {
hostPatchProp(
el,
key,
oldProps[key],
null,
isSVG,
vnode.children as VNode[],
parentComponent,
parentSuspense,
unmountChildren
)
}
}
}
if ('value' in newProps) { // 处理value的
hostPatchProp(el, 'value', oldProps.value, newProps.value)
}
}
}
总结
- 流程总结
通过render函数进行ELEMENT节点的挂载大致就是下面是大致流程:
**ELEMENT节点的挂载→render函数→patch挂载函数→type+shapeFlag→processElement→n1==null→patchElement**→ el = (n2.el = n1.el!)并记录新旧vnode的props→开始更新孩子节点patchChildren→ 获取新旧节点的children和shapeFlag→hostSetElementText(container, c2 as string) →更新孩子节点结束→开始更新props patchProps→两次for循环,一次挂载新节点的props,一次删除只有旧节点存在的属性→props更新结束
- 函数总结
- render函数:判断vnode是否为空,进行patch挂载还是unmount卸载操作。
- patch函数:根据vnode的type和shapeFlag进行不同的processXXX操作,本节中是processElement
- processElement函数:根据vnode1(n1)是否为空进行mountElement挂载、patchElement更新
- patchElement函数(同DOM):赋值元素el,记录新旧节点props,更新children节点、更新props(挂载新节点的属性、删除存在于旧节点的属性)
- patchChildren函数:根据shapeFlag的值进行不同的操作
- patchProps函数:处理节点props的属性。进行两次循环,第一次是挂载新节点的props到元素上,第二次是删除旧节点在元素el上的属性。