持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
前言
上篇我们分析了VNode的生成,并研究了为什么 VNode 的标识符使用位操作进行判断。这篇主要是继续顺着初始化流程,createVNode 之后,执行了
createAppAPI 传入的render方法进行渲染。
// packages/runtime-core/src/apiCreateApp.ts
export function createAppAPI<HostElement>(
render: RootRenderFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
mount(
rootContainer: HostElement
): any {
// 是否初始化挂载,只执行一次
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
render(vnode, rootContainer, isSVG)
isMounted = true
return getExposeProxy(vnode.component!) || vnode.component!.proxy
},
}
}
}
render
render 方法的位置是在 packages/runtime-core/src/renderer.ts 文件的 baseCreateRenderer 方法中定义的,并通过参数
传给了createAppAPI。
// packages/runtime-core/src/renderer.ts
function baseCreateRenderer(options: RendererOptions) {
// 省略
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)
}
// patch结束后,开始冲刷任务调度器中的任务
flushPostFlushCbs()
// 更新vnode
container._vnode = vnode
}
// 省略
}
render 方法接收参数 vnode、container 和 isSVG。
- 首先判断 vnode 是否存在。如果不存在,则调用unmount函数,进行组件的卸载
- 如果存在需要进行patch操作,patch的过程就包含了组件了创建到挂载,变化到更新。
- patch 结束后,会调用flushPostFlushCbs函数冲刷任务池
- 最后更新容器上的 vnode
patch
/**
n1 与 n2 是待比较的两个节点
n1 为旧节点
n2 为新节点
container 是新节点的容器
anchor 是一个锚点,用来标识当我们对新旧节点做增删或移动等操作时,以哪个节点为参照物。
optimized 参数是是否开启优化模式的标识
*/
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
// 当两个节点的类型不同,则直接卸载旧节点。
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
// 如果新节点的 patchFlag 的值是 BAIL ,优化模式会被关闭
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const { type, ref, shapeFlag } = n2
switch (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) {
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
)
}
}
}
patch 的逻辑比较多,我们逐一分析一下
- 传入的参数中 n1 与 n2 是待比较的两个节点,n1 为旧节点,n2 为新节点,container 是新节点的容器,anchor 是一个锚点,用来标识当我们对新旧节点做增删或移动等操作时,以哪个节点为参照物。optimized 参数是是否开启优化模式的标识
- 第一个条件,当旧节点存在,并且新旧节点不是同一类型时,则将旧节点从节点树中卸载。这就是我们可以总结出的 patch 的第一个逻辑: 当两个节点的类型不同,则直接卸载旧节点。
- 第二个条件,如果新节点的 patchFlag 的值是 BAIL ,优化模式会被关闭
- 接下来通过 switch 语句,判断新节点类型,分别对不同类型的节点进行操作
processText
我们来看看第一个,当新节点是文字时,会调用 processText。
const processText: ProcessTextOrCommentFn = (n1, n2, container, anchor) => {
if (n1 == null) {
hostInsert(
(n2.el = hostCreateText(n2.children as string)),
container,
anchor
)
} else {
const el = (n2.el = n1.el!)
if (n2.children !== n1.children) {
hostSetText(el, n2.children as string)
}
}
}
简单说一下 hostInsert 的功能是在 container 中的 anchor 之前插入节点 n2.el,就是调用了API container.insertBefore(n2.el, anchor);
hostCreateText 创建文本节点,调用了API createTextNode
hostSetText 为 el 设置文本
- 首先,如果旧节点不存在时,说明是新添加元素,直接创建文本节点后,插入到容器中。
- 如果旧节点存在,如果新旧节点不同,则用旧节点覆盖新节点。
小结
这节了解了,render 的逻辑,和 patch 的功能,并分析了文本节点的patch,下一篇我们继续分析其他节点的 patch