前文
上节课我们拿捏了patch函数,这节课继续往里深入拿捏
之前我们知道了
当新旧虚拟dom是相同的,则使用patchvnode函数比对
不同则删除旧元素,插入新元素。
那当相同的时候,patchvnode都干了什么呢?
开干!
先说一个结论,大家可以记忆下来
旧虚拟dom是目前网页的节点状态的抽象
新虚拟dom是将来网页的节点状态的抽象
我们需要在旧虚拟dom和新虚拟dom的比对过程中将其真实dom的状态更新至新虚拟dom。
如果理解这部分就说明已经难不倒你了
开始
首先看代码
function patchVnode(
oldVnode: VNode,
vnode: VNode,
insertedVnodeQueue: VNodeQueue
) {
const hook = vnode.data?.hook;
hook?.prepatch?.(oldVnode, vnode);
const elm = (vnode.elm = oldVnode.elm)!;
const oldCh = oldVnode.children as VNode[];
const ch = vnode.children as VNode[];
if (oldVnode === vnode) return;
if (vnode.data !== undefined) {
for (let i = 0; i < cbs.update.length; ++i)
cbs.update[i](oldVnode, vnode);
vnode.data.hook?.update?.(oldVnode, vnode);
}
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) api.setTextContent(elm, "");
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, "");
}
} else if (oldVnode.text !== vnode.text) {
if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
api.setTextContent(elm, vnode.text!);
}
hook?.postpatch?.(oldVnode, vnode);
}
oldVnode: VNode,
vnode: VNode,
insertedVnodeQueue: VNodeQueue
传入新旧Vnode以及一个inseredVnodeQueue数组,这个将在执行完这个函数后循环触发钩子。
const hook = vnode.data?.hook;
hook?.prepatch?.(oldVnode, vnode);
const elm = (vnode.elm = oldVnode.elm)!;
const oldCh = oldVnode.children as VNode[];
const ch = vnode.children as VNode[];
这里获取了新node下的data属性的hook钩子函数
并判断是否存在,如果存在则判断prepatch是否存在,存在则调用这个函数触发回调钩子。
并将旧虚拟dom的真实dom复制给新的虚拟dom的elm属性上
分别获取新旧虚拟dom的子节点。
if (oldVnode === vnode) return;
判断传入的新旧虚拟dom对象是否相等,如果相等则啥也不执行
if (vnode.data !== undefined) {
for (let i = 0; i < cbs.update.length; ++i)
cbs.update[i](oldVnode, vnode);
vnode.data.hook?.update?.(oldVnode, vnode);
}
依然是调用cbs的回调钩子,以及虚拟dom的data属性的钩子函数。
if (isUndef(vnode.text)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) api.setTextContent(elm, "");
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, "");
}
} else if (oldVnode.text !== vnode.text) {
if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
api.setTextContent(elm, vnode.text!);
}
这里的逻辑相对复杂,我们一行一行来看
首先看看isundef函数以及isdef函数是啥
function isUndef(s: any): boolean {
return s === undefined;
}
function isDef<A>(s: A): s is NonUndefined<A> {
return s !== undefined;
}
isundef判断是否等于undefine,isdef判断是否不等于undefined
if (isUndef(vnode.text))
判断vnode.text是否为undefined,如果为undefined则执行括号内内容,不为则执行下部分。
因为一部分的内容相对较少,我们先分析vnode.text不为undefined的情况
注意,vnode只存在两种情况,一种是存在文字,一种是存在子节点,为undefined则为存在子节点,不为undefined则为文字
else if (oldVnode.text !== vnode.text) {
if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
}
api.setTextContent(elm, vnode.text!);
}
判断文字是否相等,如果相等则无需进行处理,不相等则进行处理
首先判断就旧节点是否不存在文字而存在子节点
如果存在子节点则移除
移除后
设置真实dom的文本为新虚拟dom的文本。
那么vnode.text存在的情况我们已经看完了,
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);
} else if (isDef(ch)) {
if (isDef(oldVnode.text)) api.setTextContent(elm, "");
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
} else if (isDef(oldCh)) {
removeVnodes(elm, oldCh, 0, oldCh.length - 1);
} else if (isDef(oldVnode.text)) {
api.setTextContent(elm, "");
}
这里判断了新旧虚拟dom的子节点存在情况,分别为四种情况,我们来逐步分析
第一个if如果判断成功,则为新旧虚拟dom都存在子节点
这时候判断是否相等,如果不相等,则调用updateChildren进行更新。
如果第一个if判断不成功,证明其中某一个或都不存在子节点。
第二个if判断了新虚拟dom是否存在子节点,根据第一个if结合推断
可以知道这种情况为新虚拟dom存在子节点,旧虚拟dom不存在子节点
这时候判断旧虚拟dom是否存在文字,如果存在则设置为空,并在真实dom上插入新虚拟dom的元素。
第三个if证明了新虚拟dom不存在子节点,并判断旧虚拟dom是否存在子节点
如果存在,则移除所有旧虚拟dom的节点。
那新虚拟dom的操作哪去了?
这里是
if (isUndef(vnode.text))
判断成功的操作,新虚拟dom不存在文字,也不存在节点
第三种情况不存在任何新节点的插入
所以我们仅需移除旧节点即可。
if (isDef(oldVnode.text)) {
api.setTextContent(elm, "");
}
第四种情况与第三种基本同理
只是旧虚拟dom不存在子节点
而存在文字
这时候我们设置为空即可
那么到这里我们已经走完了所有的if流程
最后完成
hook?.postpatch?.(oldVnode, vnode);
触发hook的回调函数即可。
这里并不在我们研究diff的范畴内,所以暂且不表。