1、核心入口代码:
const setupRenderEffect: SetupRenderEffectFn = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
// 创建响应式的副作用渲染函数
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
//渲染组件
} else {
// 更新组件
let { next, bu, u, parent, vnode } = instance // next 表示新的组件 vnode
if (next) {
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// 渲染新的子树 vnode
const nextTree = renderComponentRoot(instance) // 缓存旧的子树 vnode
const prevTree = instance.subTree
// 更新子树 vnode
instance.subTree = nextTree
// 组件更新核心逻辑,根据新旧子树 vnode 做 patch
patch(prevTree,
nextTree,
// 如果在 teleport 组件中父节点可能已经改变,所以容器直接找旧树 DOM 元素的父节点
hostParentNode(prevTree.el!)!,
// 参考节点在 fragment 的情况可能改变,所以直接找旧树 DOM 元素的下一个节点
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
// 缓存更新后的 DOM 节点
next.el = nextTree.el
}
}, prodEffectOptions)}
2、核心逻辑:patch 流程
const patch: PatchFn = (n1,n2,container,anchor = null,parentComponent = null,parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false ) => {
// 如果存在新旧节点, 且新旧节点类型不同,则销毁旧节点
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text: // 处理文本节点
break
case Comment:// 处理注释节点
break
case Static:// 处理静态节点
break
case Fragment:// 处理 Fragment 元素
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {// 处理普通 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
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) { // 处理 SUSPENSE
}
}
}
3、组件更新:
const processComponent = (n1: VNode | null, n2: VNode, container: RendererElement,
anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null, isSVG: boolean, slotScopeIds: string[] | null,
optimized: boolean ) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
// 挂载组件
} else {
//更新子组件
updateComponent(n1, n2, optimized)
}
}
const updateComponent = (n1: VNode, n2: VNode, optimized: boolean) => {
const instance = (n2.component = n1.component)!
// 根据新旧子组件 vnode 判断是否需要更新子组件
if (shouldUpdateComponent(n1, n2, optimized)) {
// 新的子组件 vnode 赋值给 instance.next
instance.next = n2
// 子组件也可能因为数据变化被添加到更新队列里了,移除它们防止对一个子组件重复更新
invalidateJob(instance.update)
// 执行子组件的副作用渲染函数
instance.update()
}
} else {
// 不需要更新,只复制属性
n2.component = n1.component
n2.el = n1.el
instance.vnode = n2
}
}
可以看到,processComponent 主要通过执行 updateComponent 函数来更新子组件,updateComponent 函数在更新子组件的时候,会先执行 shouldUpdateComponent 函数,根据新旧子组件 vnode 来判断是否需要更新子组件。这里你只需要知道,在 shouldUpdateComponent 函数的内部,主要是通过检测和对比组件 vnode 中的 props、chidren、dirs、transiton 等属性,来决定子组件是否需要更新.
4、普通元素更新:
const processElement = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
if (n1 == null) {
// 挂载元素 }
else {
// 更新元素
patchElement(
n1,
n2,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
const patchElement = (
n1: VNode,
n2: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean ) => {
const el = (n2.el = n1.el!)
const oldProps = n1.props || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
patchChildren(
n1,
n2,
el,
null,
parentComponent,
parentSuspense,
areChildrenSVG,
slotScopeIds,
false
)
}
可以看到,更新元素的过程主要做两件事情:更新 props 和更新子节点。其实这是很好理解的,因为一个 DOM 节点元素就是由它自身的一些属性和子节点构成的。